CMD Guide
HomeConcurrencyConcurrency Problems

Problem 9 Print in Order using multithreading

Problem Statement

Ensure the ordered execution of methods across different threads. We have three methods — first(), second(), and third() — and we must guarantee that first() completes before second(), and second() completes before third(), regardless of the order in which the threads are started or scheduled.

Solution

We use two CountDownLatch objects as one-shot signals between threads. A CountDownLatch initialized to 1 blocks any thread that calls await() until another thread calls countDown().

This is a minimal-change, coherent design: even if the JVM schedules third() before first(), the latch forces third() to wait until second() has run, and second() waits until first() has run.

Trace — reverse start order (third, second, first)

Threads are scheduled in the worst order; latches still force print order:

#ThreadActionfirstDonesecondDoneOutput
1T3secondDone.await() blocks11
2T2firstDone.await() blocks11
3T1print first; firstDone.countDown()01first
4T2unblocks; print second; secondDone.countDown()00firstsecond
5T3unblocks; print third00firstsecondthird

Start order ≠ run order. The signal chain is the only authority.

Why this latch can't miss its signal: level-triggered, not edge-triggered

The trace above shows the awaiters blocking first and then being released — but the design is correct even in the opposite race, and understanding why is the point an interviewer digs at. A CountDownLatch is level-triggered: its count is durable state, and await() is a test of that state, not a wait for an event. Once countDown() drives the count to 0 it stays 0, so a later await() reads “already 0” and returns immediately — the signal cannot be missed because it was never a fleeting pulse, it is a persistent fact.

Play the reordering the reverse-start trace didn’t cover — first() runs entirely before second() ever reaches its wait:

#ThreadActionfirstDoneResult
1T1print first; firstDone.countDown()0count is now a persistent 0
2T2firstDone.await() — sees count already 00returns at once, no block

Now contrast the edge-triggered primitive, Object.notify() / Condition.signal(). A notify is a one-shot pulse delivered only to threads currently waiting; it is not remembered. Run the same race with a bare monitor: if T1 calls obj.notify() before T2 has called obj.wait(), the pulse wakes nobody, T2 then wait()s, and — with no further signal coming — hangs forever. That is the classic lost wakeup. The only fix is to pair the condition with durable state and re-check it in a loop (while (!done) cond.await();) — i.e. you must manually rebuild the level-triggered behaviour the latch gives you for free. This is exactly why the latch (and the semaphore, whose released permit is likewise remembered) is the right primitive for one-shot ordering, while a raw condition variable needs the defensive while-loop to be safe.

Code

import java.util.concurrent.CountDownLatch;

/**
 * This Foo class ensures that its methods are called in a specific order:
 * `first`, followed by `second`, and then `third`.
 */
public class Solution {

  // CountDownLatches are synchronization aids that allow one or more threads to wait
  // until a set of operations being performed in other threads completes.

  // This latch ensures that `second` waits until `first` has finished executing.
  private final CountDownLatch firstDone = new CountDownLatch(1);

  // This latch ensures that `third` waits until `second` has finished executing.
  private final CountDownLatch secondDone = new CountDownLatch(1);

  public Solution() {}

  public void first() {
    // This is the first method, and it can proceed without waiting.
    print("first");
    // Decrement the count of the latch, signaling that this method is done.
    firstDone.countDown();
  }

  public void second() throws InterruptedException {
    // Wait for the first method to complete.
    firstDone.await();
    print("second");
    // Signal that the second method has now completed.
    secondDone.countDown();
  }

  public void third() throws InterruptedException {
    // Wait for the second method to complete.
    secondDone.await();
    print("third");
  }

  private void print(String msg) {
    System.out.print(msg);
  }

  public static void main(String[] args) {
    Solution foo = new Solution();

    Thread threadA = new Thread(() -> {
      try {
        foo.first();
      } catch (Exception e) {
        e.printStackTrace();
      }
    });

    Thread threadB = new Thread(() -> {
      try {
        foo.second();
      } catch (Exception e) {
        e.printStackTrace();
      }
    });

    Thread threadC = new Thread(() -> {
      try {
        foo.third();
      } catch (Exception e) {
        e.printStackTrace();
      }
    });

    // The actual order in which the threads start and get scheduled is up to the
    // JVM and the OS, but the CountDownLatches ensure they complete as first,
    // second, third.
    threadA.start();
    threadB.start();
    threadC.start();
  }
}

Go version: channel gates (close-as-signal)

Idiomatic dual: two channels; first closes g2; second receives g2 then closes g3; third receives g3. close happens-before every receive that sees the closed channel — same edge as latch countDown→await.

package main

import (
    "fmt"
    "sync"
)

func main() {
    g2, g3 := make(chan struct{}), make(chan struct{})
    var wg sync.WaitGroup
    wg.Add(3)

    // start in reverse order — still prints firstsecondthird
    go func() {
        defer wg.Done()
        <-g3
        fmt.Print("third")
    }()
    go func() {
        defer wg.Done()
        <-g2
        fmt.Print("second")
        close(g3)
    }()
    go func() {
        defer wg.Done()
        fmt.Print("first")
        close(g2)
    }()

    wg.Wait()
    fmt.Println()
}

Which primitive should you use?

PrimitiveBest forTrade-off
CountDownLatchOne-shot "wait for step N" ordering, exactly as here.Simple, but cannot be reused and cannot signal multiple phases cleanly.
Semaphore (permits 0..1)Same one-shot ordering; also useful if you want to release multiple waiters.More flexible, but overkill for a binary signal and easier to misconfigure.
PhaserMulti-phase barriers where the same threads meet repeatedly.Heavier API; only worth it if the problem has several rounds, not a single ordered chain.
Condition variablesOrdering tied to a mutable state predicate (e.g. "queue is non-empty").Requires a lock and careful while-loop re-checks; unnecessary for pure ordering.

For a fixed three-step pipeline, CountDownLatch is the clearest answer; channels are the idiomatic Go equivalent.

Time Complexity

O(1) per method call. await() blocks until the corresponding signal, but does no extra work.

Space Complexity

O(1) — only two latch objects are allocated.

Takeaways


Problem and solution structure adapted from DesignGurus. Re-authored and corrected for this guide.

When NOT to over-engineer Print-in-Order

Interviewer follow-ups & drills

  1. Why not sleep/retry? Timing hacks race under load; use happens-before via lock/sem/channel.
  2. Failure: missed signal → permanent hang; always check condition in loop (spurious wakeups).
  3. Drill: three threads first/second/third — show semaphore gate or channel handoff order.
🔨 Practice this hands-on — Build Print in Order →
Attempt it from an empty file, break it to feel the failure, then defend it under pushback.
🤖 Don't fully get this? Learn it with Claude

Stuck on Problem 9 Print in Order using multithreading? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Problem 9 Print in Order using multithreading** (Concurrency). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Problem 9 Print in Order using multithreading** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Problem 9 Print in Order using multithreading**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Problem 9 Print in Order using multithreading**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes