Interviewers love CountDownLatch because it tests whether you can coordinate threads without overcomplicating the design.
Question: What is CountDownLatch in Java, and when would you use it?
Answer: CountDownLatch is a synchronization tool that lets one or more threads wait until a set of work items finishes. You create it with a fixed count, call countDown() as each task completes, and call await() in the waiting thread. It is one-time use: once the count reaches zero, it stays open forever.
Interview-Ready Answer: I use CountDownLatch when I want one thread to wait for several other tasks to finish before continuing, like waiting for three service calls or worker threads during startup. I initialize it with the number of completions I need, have each worker call countDown() in a finally block, and have the coordinator call await(). A nice detail is that it provides a memory-visibility guarantee: work done before countDown() becomes visible after a successful await(). The key limitation is that it is one-shot, so if I need something reusable, I would look at CyclicBarrier or Phaser.
CountDownLatch is a simple gate. Think of a room with a locked door and a counter on the wall. Each task finishes, presses the button once, and when the counter hits zero the door opens.
new CountDownLatch(3). That number is stored internally as shared state.await(). If the count is still above zero, they block, which means the thread stops running and waits in the scheduler queue.countDown() when its job is done. If this call reduces the count to zero, the latch releases all waiting threads.await() returns successfully, the waiting thread can safely continue. Java also gives a happens-before guarantee, meaning earlier writes in the worker threads become visible to the waiting thread.countDown() usually belongs in a finally block.Use it when the flow is one-way: start here, wait for N completions, then continue. Common examples are application warmup, parallel data fetches, and tests that need multiple threads to finish before assertions run. Do not use it when you need to reuse the same coordination point many times.
| Tool | Best for | Reusable? | Key difference |
|---|---|---|---|
CountDownLatch | Wait for N completions | No | One-shot gate |
CyclicBarrier | Threads meet at a phase | Yes | All parties wait together |
Phaser | Multi-phase coordination | Yes | More flexible, more complex |
Thread.join() | Wait for one thread | N/A | Joins a specific thread only |
The basic operations are effectively O(1): countDown() and await() do a tiny amount of bookkeeping, then park or release threads as needed. Memory cost is small, because the latch stores only the count and waiting state. Common edge cases are forgetting a countdown, waiting forever, or using the latch after it already reached zero and assuming it can be reset. It cannot. If you need a timeout, use await(timeout, unit) so the system can fail fast instead of hanging forever.
Memory hook: “A latch is a one-way door: each task turns one key, and the last key opens it forever.”
Imagine a checkout service that starts three warmup jobs in parallel: load promo rules, fetch payment config, and prime the product cache. The main server thread uses a CountDownLatch to wait until all three jobs finish before accepting traffic.
What goes wrong: one warmup task throws an exception before calling countDown(). Startup never finishes, the service sits “healthy but not ready,” and logs show the main thread stuck in await(). In production, this looks like a deployment that never opens traffic, repeated readiness probe failures, and frustrated operators seeing no clear error unless the timeout path is in place.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
int workers = 3;
CountDownLatch doneLatch = new CountDownLatch(workers);
ExecutorService pool = Executors.newFixedThreadPool(workers);
List<Callable<Void>> tasks = new ArrayList<>();
tasks.add(() -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
System.out.println("Task 1: loaded config");
} finally {
// Always count down in finally so an exception cannot trap the waiter forever.
doneLatch.countDown();
}
return null;
});
tasks.add(() -> {
try {
TimeUnit.MILLISECONDS.sleep(500);
// Simulate a real failure path.
throw new IllegalStateException("Task 2: remote service failed");
} finally {
// Even on failure, we still signal completion of this worker's attempt.
doneLatch.countDown();
}
});
tasks.add(() -> {
try {
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("Task 3: warmed cache");
} finally {
doneLatch.countDown();
}
return null;
});
for (Callable<Void> task : tasks) {
pool.submit(() -> {
try {
task.call();
} catch (Exception e) {
System.out.println("Worker error: " + e.getMessage());
}
});
}
// Wait with a timeout so the application fails fast instead of hanging forever.
boolean finished = doneLatch.await(2, TimeUnit.SECONDS);
System.out.println("All workers finished within timeout: " + finished);
System.out.println("Latch count after await: " + doneLatch.getCount());
// Extra countDown calls after zero do nothing. This proves the latch is one-shot.
doneLatch.countDown();
System.out.println("Latch count after extra countDown(): " + doneLatch.getCount());
pool.shutdown();
if (!pool.awaitTermination(2, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
}
}Follow-up & Tricky Questions:
CountDownLatch different from CyclicBarrier? CountDownLatch is one-way and one-time; CyclicBarrier is reusable and designed for a group of threads to all wait at the same point before moving on.countDown() is called more times than the initial count? Nothing bad happens after it reaches zero; the count stays at zero and extra calls have no further effect.countDown() usually be in a finally block? Because exceptions can skip normal code paths. Putting it in finally ensures the latch is released even when the task fails.await(timeout, unit) return? It returns true if the latch reached zero in time, otherwise false. That is crucial for building safe startup or retry logic.countDown() unblock multiple waiting threads? Yes. Once the count hits zero, all threads waiting in await() are released.Tricky questions:
CountDownLatch track which thread counted down? No, it only tracks the count. Any thread can call countDown().synchronized? No. synchronized protects shared data; CountDownLatch coordinates progress between threads.await(), is that a problem? No. The count is already lower, so the later await() returns immediately once the count is zero.Common Mistakes:
countDown() in finally.CyclicBarrier or Phaser for repeated phases.await(). Fix: use a timeout in production paths.Memory Hook: One-way door: every task turns one key, and the last key opens the gate forever.
Cheat Sheet:
countDown(); waiters call await().finally to avoid deadlocks from exceptions.await(timeout, unit) for safety.Practice Tasks:
CyclicBarrier and compare the behavior.