1115. Print FooBar Alternately
Problem Description
You have a class FooBar with two methods: foo() and bar(). Each method prints its respective string ("foo" or "bar") n times in a loop.
Two separate threads will be created:
- Thread A will call the
foo()method - Thread B will call the
bar()method
Both threads will run concurrently on the same instance of FooBar. Without synchronization, the output could be in any order (like "foofoofoobarbar" or "barbarfoofoo").
Your task is to modify the program using synchronization mechanisms to ensure that the output always alternates between "foo" and "bar", producing exactly "foobar" repeated n times. For example:
- If
n = 1, the output should be: "foobar" - If
n = 2, the output should be: "foobarfoobar" - If
n = 3, the output should be: "foobarfoobarfoobar"
The solution uses two semaphores to coordinate the threads:
- Semaphore
f(initialized to 1) controls when "foo" can be printed - Semaphore
b(initialized to 0) controls when "bar" can be printed
The synchronization works as follows:
- Thread A acquires semaphore
f, prints "foo", then releases semaphoreb - Thread B acquires semaphore
b, prints "bar", then releases semaphoref - This pattern repeats
ntimes, ensuring strict alternation
The semaphores act as signals between threads - when one thread finishes printing, it signals the other thread that it's their turn to print. This guarantees the correct "foobar" pattern regardless of thread scheduling.
Intuition
When we have two threads that need to alternate their execution in a strict pattern, we need a way for them to communicate and coordinate. Think of it like two people taking turns speaking - one person needs to signal when they're done so the other knows it's their turn.
The key insight is that we need to enforce a specific ordering: "foo" must always come before "bar", and after "bar" is printed, "foo" can print again. This is a classic producer-consumer pattern where one thread produces a signal for the other to consume.
We can achieve this coordination using semaphores, which act like permission slips. A semaphore with value 1 means "you have permission to proceed," while a value of 0 means "you must wait."
Here's the reasoning:
-
Initially, we want "foo" to print first, so we give the
foothread permission to start (semaphoref = 1) while making thebarthread wait (semaphoreb = 0). -
After "foo" prints, it needs to tell "bar" that it's now bar's turn. This is done by releasing semaphore
b(changing it from 0 to 1), giving thebarthread permission to proceed. -
Similarly, after "bar" prints, it needs to tell "foo" that foo can go again. This is done by releasing semaphore
f. -
The acquire operations ensure that each thread waits for its turn -
foowaits for semaphorefto be available, andbarwaits for semaphorebto be available.
This creates a ping-pong effect: foo → signals bar → bar → signals foo → foo → signals bar... continuing for n iterations. The semaphores essentially create a token-passing mechanism where only the thread holding the token can execute, and after execution, it passes the token to the other thread.
Solution Approach
The implementation uses two semaphores to control the execution order of the threads. Let's walk through how this works:
Initialization:
- Create semaphore
fwith initial value1- this allows thefoothread to execute first - Create semaphore
bwith initial value0- this blocks thebarthread initially
The foo method execution:
for _ in range(self.n):
self.f.acquire() # Wait for permission to print "foo"
printFoo() # Print "foo"
self.b.release() # Give permission for "bar" to print
self.f.acquire()- The thread attempts to acquire semaphoref. On the first iteration,f = 1, so it proceeds immediately and decrementsfto0. On subsequent iterations, it waits until thebarthread releasesf.printFoo()- Once acquired, it prints "foo"self.b.release()- It then releases semaphoreb, incrementing it from0to1, signaling thebarthread that it can now proceed
The bar method execution:
for _ in range(self.n):
self.b.acquire() # Wait for permission to print "bar"
printBar() # Print "bar"
self.f.release() # Give permission for "foo" to print
self.b.acquire()- The thread attempts to acquire semaphoreb. Initiallyb = 0, so it blocks until thefoothread releasesb. Once available, it decrementsbto0.printBar()- Once acquired, it prints "bar"self.f.release()- It then releases semaphoref, incrementing it from0to1, allowing thefoothread to proceed with its next iteration
Execution Flow for n = 2:
- Initial state:
f = 1,b = 0 - Thread A (iteration 1): acquires
f(nowf = 0), prints "foo", releasesb(nowb = 1) - Thread B (iteration 1): acquires
b(nowb = 0), prints "bar", releasesf(nowf = 1) - Thread A (iteration 2): acquires
f(nowf = 0), prints "foo", releasesb(nowb = 1) - Thread B (iteration 2): acquires
b(nowb = 0), prints "bar", releasesf(nowf = 1) - Output: "foobarfoobar"
The semaphores create a strict alternation pattern where each thread must wait for the other to complete before proceeding, ensuring the output is always in the correct "foobar" order repeated n times.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with n = 2 to see how the semaphores coordinate the threads.
Initial Setup:
- Semaphore
f = 1(foo can start) - Semaphore
b = 0(bar must wait) - Thread A runs
foo()method - Thread B runs
bar()method
Step-by-Step Execution:
| Step | Thread A (foo) | Thread B (bar) | Semaphore f | Semaphore b | Output |
|---|---|---|---|---|---|
| 1 | f.acquire() - succeeds, f becomes 0 | b.acquire() - blocks (b is 0) | 0 | 0 | |
| 2 | printFoo() - prints "foo" | (still blocked) | 0 | 0 | "foo" |
| 3 | b.release() - b becomes 1 | (still blocked) | 0 | 1 | "foo" |
| 4 | Starts iteration 2: f.acquire() - blocks (f is 0) | b.acquire() - succeeds, b becomes 0 | 0 | 0 | "foo" |
| 5 | (still blocked) | printBar() - prints "bar" | 0 | 0 | "foobar" |
| 6 | (still blocked) | f.release() - f becomes 1 | 1 | 0 | "foobar" |
| 7 | f.acquire() - succeeds, f becomes 0 | Starts iteration 2: b.acquire() - blocks (b is 0) | 0 | 0 | "foobar" |
| 8 | printFoo() - prints "foo" | (still blocked) | 0 | 0 | "foobarfoo" |
| 9 | b.release() - b becomes 1 | (still blocked) | 0 | 1 | "foobarfoo" |
| 10 | Loop complete | b.acquire() - succeeds, b becomes 0 | 0 | 0 | "foobarfoo" |
| 11 | printBar() - prints "bar" | 0 | 0 | "foobarfoobar" | |
| 12 | f.release() - f becomes 1 | 1 | 0 | "foobarfoobar" |
Key Observations:
- Thread A always waits for semaphore
fbefore printing "foo" - Thread B always waits for semaphore
bbefore printing "bar" - After printing, each thread signals the other by releasing the opposite semaphore
- The initial values (
f=1, b=0) ensure "foo" prints first - The alternating acquire/release pattern creates a handshake mechanism that enforces strict ordering
This synchronization guarantees that regardless of how the operating system schedules these threads, the output will always be "foobarfoobar" for n=2.
Solution Implementation
1from threading import Semaphore
2from typing import Callable
3
4
5class FooBar:
6 """
7 A class that coordinates two threads to print "foo" and "bar" alternately.
8
9 This implementation uses semaphores to ensure that "foo" and "bar" are printed
10 in alternating order, regardless of which thread runs first or how the threads
11 are scheduled.
12 """
13
14 def __init__(self, n: int) -> None:
15 """
16 Initialize the FooBar synchronization object.
17
18 Args:
19 n: The number of times each method (foo and bar) should print.
20 """
21 self.n = n
22 # Semaphore for foo method - initially available (value=1)
23 # This allows foo to run first
24 self.foo_semaphore = Semaphore(1)
25
26 # Semaphore for bar method - initially blocked (value=0)
27 # This ensures bar waits for foo to complete first
28 self.bar_semaphore = Semaphore(0)
29
30 def foo(self, printFoo: Callable[[], None]) -> None:
31 """
32 Print "foo" n times, alternating with bar method.
33
34 Args:
35 printFoo: A callable that prints "foo" when invoked.
36 """
37 for _ in range(self.n):
38 # Wait for permission to print foo
39 # First iteration: semaphore is available (value=1)
40 # Subsequent iterations: wait for bar to release this semaphore
41 self.foo_semaphore.acquire()
42
43 # printFoo() outputs "foo". Do not change or remove this line.
44 printFoo()
45
46 # Signal bar method that foo has completed
47 # This allows bar to proceed with its print
48 self.bar_semaphore.release()
49
50 def bar(self, printBar: Callable[[], None]) -> None:
51 """
52 Print "bar" n times, alternating with foo method.
53
54 Args:
55 printBar: A callable that prints "bar" when invoked.
56 """
57 for _ in range(self.n):
58 # Wait for foo to complete before printing bar
59 # First iteration: blocked until foo releases this semaphore
60 # Subsequent iterations: wait for foo to release this semaphore
61 self.bar_semaphore.acquire()
62
63 # printBar() outputs "bar". Do not change or remove this line.
64 printBar()
65
66 # Signal foo method that bar has completed
67 # This allows foo to proceed with its next iteration
68 self.foo_semaphore.release()
691class FooBar {
2 private int n;
3 // Semaphore for controlling "foo" execution - initialized with 1 permit (foo goes first)
4 private Semaphore fooSemaphore = new Semaphore(1);
5 // Semaphore for controlling "bar" execution - initialized with 0 permits (bar waits)
6 private Semaphore barSemaphore = new Semaphore(0);
7
8 /**
9 * Constructor to initialize the FooBar instance with the number of iterations
10 * @param n Number of times to print "foobar"
11 */
12 public FooBar(int n) {
13 this.n = n;
14 }
15
16 /**
17 * Method executed by the "foo" thread
18 * Prints "foo" n times, alternating with bar
19 * @param printFoo Runnable that prints "foo"
20 * @throws InterruptedException if thread is interrupted while waiting
21 */
22 public void foo(Runnable printFoo) throws InterruptedException {
23 for (int i = 0; i < n; i++) {
24 // Wait for permission to print "foo" (acquire permit from fooSemaphore)
25 fooSemaphore.acquire();
26
27 // printFoo.run() outputs "foo". Do not change or remove this line.
28 printFoo.run();
29
30 // Signal that "bar" can now execute (release permit to barSemaphore)
31 barSemaphore.release();
32 }
33 }
34
35 /**
36 * Method executed by the "bar" thread
37 * Prints "bar" n times, alternating with foo
38 * @param printBar Runnable that prints "bar"
39 * @throws InterruptedException if thread is interrupted while waiting
40 */
41 public void bar(Runnable printBar) throws InterruptedException {
42 for (int i = 0; i < n; i++) {
43 // Wait for permission to print "bar" (acquire permit from barSemaphore)
44 barSemaphore.acquire();
45
46 // printBar.run() outputs "bar". Do not change or remove this line.
47 printBar.run();
48
49 // Signal that "foo" can now execute (release permit to fooSemaphore)
50 fooSemaphore.release();
51 }
52 }
53}
541#include <semaphore.h>
2#include <functional>
3
4class FooBar {
5private:
6 int n; // Number of times to print "foobar"
7 sem_t semaphoreFoo; // Semaphore to control foo() execution
8 sem_t semaphoreBar; // Semaphore to control bar() execution
9
10public:
11 FooBar(int n) {
12 this->n = n;
13 // Initialize semaphoreFoo with value 1 (foo goes first)
14 sem_init(&semaphoreFoo, 0, 1);
15 // Initialize semaphoreBar with value 0 (bar waits initially)
16 sem_init(&semaphoreBar, 0, 0);
17 }
18
19 void foo(std::function<void()> printFoo) {
20 for (int i = 0; i < n; i++) {
21 // Wait for permission to print "foo"
22 sem_wait(&semaphoreFoo);
23
24 // printFoo() outputs "foo". Do not change or remove this line.
25 printFoo();
26
27 // Signal bar() that it can now print "bar"
28 sem_post(&semaphoreBar);
29 }
30 }
31
32 void bar(std::function<void()> printBar) {
33 for (int i = 0; i < n; i++) {
34 // Wait for foo() to complete before printing "bar"
35 sem_wait(&semaphoreBar);
36
37 // printBar() outputs "bar". Do not change or remove this line.
38 printBar();
39
40 // Signal foo() that it can print "foo" again
41 sem_post(&semaphoreFoo);
42 }
43 }
44};
451// Number of times to print "foobar"
2let n: number;
3
4// Semaphore-like structures using Promises
5let semaphoreFoo: Promise<void>;
6let semaphoreBar: Promise<void>;
7let resolveFoo: (() => void) | null = null;
8let resolveBar: (() => void) | null = null;
9
10/**
11 * Initializes the synchronization mechanism
12 * @param count - Number of times to print "foobar"
13 */
14function initializeFooBar(count: number): void {
15 n = count;
16
17 // Initialize semaphoreFoo with immediate resolution (foo goes first)
18 semaphoreFoo = Promise.resolve();
19
20 // Initialize semaphoreBar with pending promise (bar waits initially)
21 semaphoreBar = new Promise<void>((resolve) => {
22 resolveBar = resolve;
23 });
24}
25
26/**
27 * Prints "foo" n times, alternating with bar()
28 * @param printFoo - Callback function that prints "foo"
29 */
30async function foo(printFoo: () => void): Promise<void> {
31 for (let i = 0; i < n; i++) {
32 // Wait for permission to print "foo"
33 await semaphoreFoo;
34
35 // Create new promise for next iteration of foo
36 semaphoreFoo = new Promise<void>((resolve) => {
37 resolveFoo = resolve;
38 });
39
40 // printFoo() outputs "foo". Do not change or remove this line.
41 printFoo();
42
43 // Signal bar() that it can now print "bar"
44 if (resolveBar) {
45 resolveBar();
46 }
47 }
48}
49
50/**
51 * Prints "bar" n times, alternating with foo()
52 * @param printBar - Callback function that prints "bar"
53 */
54async function bar(printBar: () => void): Promise<void> {
55 for (let i = 0; i < n; i++) {
56 // Wait for foo() to complete before printing "bar"
57 await semaphoreBar;
58
59 // Create new promise for next iteration of bar
60 semaphoreBar = new Promise<void>((resolve) => {
61 resolveBar = resolve;
62 });
63
64 // printBar() outputs "bar". Do not change or remove this line.
65 printBar();
66
67 // Signal foo() that it can print "foo" again
68 if (resolveFoo) {
69 resolveFoo();
70 }
71 }
72}
73Time and Space Complexity
The time complexity is O(n), where n is the parameter passed to the constructor. This is because both the foo and bar methods execute their loops exactly n times. Each iteration performs a constant amount of work - acquiring a semaphore, calling a print function, and releasing a semaphore are all O(1) operations. Therefore, the total time complexity is O(n) * O(1) = O(n).
The space complexity is O(1). The class only uses a fixed amount of extra space regardless of the input size n. It stores:
- One integer variable
self.n - Two semaphore objects
self.fandself.b
The semaphores themselves use constant space as they only maintain internal state (counter and queue) that doesn't grow with n. The loop variables in both methods also use constant space. Since the space usage doesn't scale with the input size, the space complexity is O(1).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Semaphore Initialization Values
One of the most common mistakes is initializing both semaphores with the same value or swapping their initial values. This can lead to deadlock or incorrect output ordering.
Incorrect Implementation:
def __init__(self, n: int) -> None:
self.n = n
# Wrong: Both initialized to 1
self.foo_semaphore = Semaphore(1)
self.bar_semaphore = Semaphore(1) # Should be 0!
Problem: If both semaphores start at 1, both threads can execute simultaneously on their first iteration, potentially producing "barfoo" instead of "foobar".
Another Incorrect Version:
def __init__(self, n: int) -> None:
self.n = n
# Wrong: Swapped initial values
self.foo_semaphore = Semaphore(0) # Should be 1!
self.bar_semaphore = Semaphore(1) # Should be 0!
Problem: This causes "bar" to print first, resulting in "barfoo" pattern instead of "foobar".
2. Swapping acquire() and release() Operations
Another critical mistake is confusing which semaphore to acquire versus which to release in each method.
Incorrect Implementation:
def foo(self, printFoo: Callable[[], None]) -> None:
for _ in range(self.n):
self.bar_semaphore.acquire() # Wrong semaphore!
printFoo()
self.foo_semaphore.release() # Wrong semaphore!
Problem: This creates a deadlock immediately. The foo thread tries to acquire a semaphore that starts at 0, blocking forever since only the bar thread can release it, but bar is also waiting.
3. Using a Single Semaphore
Some might attempt to use just one semaphore, thinking it's simpler.
Incorrect Implementation:
def __init__(self, n: int) -> None:
self.n = n
self.semaphore = Semaphore(1)
def foo(self, printFoo: Callable[[], None]) -> None:
for _ in range(self.n):
self.semaphore.acquire()
printFoo()
self.semaphore.release()
def bar(self, printBar: Callable[[], None]) -> None:
for _ in range(self.n):
self.semaphore.acquire()
printBar()
self.semaphore.release()
Problem: A single semaphore only provides mutual exclusion but doesn't enforce ordering. The output could be "foofoobarbar" or "barbarfoofoo" or any other combination.
4. Missing the Loop Structure
Forgetting that each method needs to execute n times, not just once.
Incorrect Implementation:
def foo(self, printFoo: Callable[[], None]) -> None:
# Missing loop - only executes once!
self.foo_semaphore.acquire()
printFoo()
self.bar_semaphore.release()
Problem: This would only print "foobar" once regardless of the value of n.
5. Race Condition in Initialization
While less common, attempting to use other synchronization primitives incorrectly:
Incorrect Implementation Using Lock:
def __init__(self, n: int) -> None:
self.n = n
self.lock = threading.Lock()
self.turn = "foo"
def foo(self, printFoo: Callable[[], None]) -> None:
for _ in range(self.n):
while True:
with self.lock:
if self.turn == "foo":
printFoo()
self.turn = "bar"
break
# Busy waiting - inefficient!
def bar(self, printBar: Callable[[], None]) -> None:
for _ in range(self.n):
while True:
with self.lock:
if self.turn == "bar":
printBar()
self.turn = "foo"
break
# Busy waiting - inefficient!
Problem: While this might work, it uses busy-waiting (spinning), which wastes CPU cycles. Semaphores are more efficient as they block the thread properly.
Correct Solution Reminder:
The correct approach uses two semaphores with specific initial values:
foo_semaphorestarts at 1 (allowing foo to go first)bar_semaphorestarts at 0 (forcing bar to wait)- Each method acquires its own semaphore and releases the other's semaphore
- This creates a ping-pong effect ensuring strict alternation
Which of the following is a min heap? 
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!