CountDownLatch in Java: Complete Guide with Real-World Examples, ExecutorService, Interview Questions, and Best Practices
Concurrency is one of the most important topics for Java backend developers. During interviews at companies like Oracle, Walmart, Amazon, Microsoft, and many product-based companies, you may beβ¦
Concurrency is one of the most important topics for Java backend developers. During interviews at companies like Oracle, Walmart, Amazon, Microsoft, and many product-based companies, you may be asked questions such as:
- What is
CountDownLatch? - Why do we use it?
- How does it work with
ExecutorService? - Does the latch count have to equal the thread count?
- Why should
countDown()always be called inside afinallyblock?
In this guide, you’ll learn everything about CountDownLatch with simple explanations, diagrams, real-world use cases, and complete code examples.
What is CountDownLatch?
CountDownLatch is a synchronization utility provided by Java that allows one or more threads to wait until a specified number of tasks have completed.
Think of it as a countdown timer.
Initial Count = 3
Task 1 Completed
Count = 2
Task 2 Completed
Count = 1
Task 3 Completed
Count = 0
β
Waiting thread continues
The waiting thread remains blocked until the count reaches zero.
Real-World Analogy
Imagine a teacher is waiting for three students to submit their assignments.
Teacher
Waiting...
Student A β
Student B β
Student C β
β
Teacher starts checking papers
The teacher cannot begin until all three students have submitted their work.
CountDownLatch behaves exactly like this.
Why Do We Need CountDownLatch?
Suppose an e-commerce application needs information from multiple services before displaying the checkout page.
Checkout Page
β
βΌ
Product Service
User Service
Order Service
β
βΌ
Wait until all complete
β
βΌ
Return Response
Without CountDownLatch, coordinating multiple threads becomes difficult.
How CountDownLatch Works
There are only two important methods.
1. countDown()
Reduces the count by one.
latch.countDown();
Example:
Count = 3
Task 1 Finished
Count = 2
2. await()
Makes the current thread wait.
latch.await();
The waiting thread resumes only when the count becomes zero.
Basic Example
CountDownLatch latch = new CountDownLatch(3);
Thread 1
Thread 2
Thread 3
β
Each thread calls
latch.countDown();
β
Count becomes 0
β
Main thread continues
Complete Example with ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(3);
CountDownLatch latch = new CountDownLatch(3);
executor.submit(() -> {
try {
System.out.println("Calling Product Service");
} finally {
latch.countDown();
}
});
executor.submit(() -> {
try {
System.out.println("Calling User Service");
} finally {
latch.countDown();
}
});
executor.submit(() -> {
try {
System.out.println("Calling Order Service");
} finally {
latch.countDown();
}
});
latch.await();
System.out.println("All services completed");
executor.shutdown();
Execution Flow
Main Thread
β
βΌ
Submit 3 Tasks
β
βΌ
Thread 1 β Product Service
Thread 2 β User Service
Thread 3 β Order Service
β
βΌ
Each task calls countDown()
3
β
2
β
1
β
0
β
await() returns
β
Main thread continues
Does CountDownLatch Count Have to Equal the Thread Count?
No.
This is one of the most commonly asked interview questions.
Many developers think these two values must be the same.
They do not represent the same thing.
Thread Pool
ExecutorService executor =
Executors.newFixedThreadPool(3);
The number 3 means
Maximum 3 threads can execute tasks at the same time.
CountDownLatch
CountDownLatch latch =
new CountDownLatch(10);
The number 10 means
Wait until 10 tasks have completed.
These are completely independent.
Example
ExecutorService
3 Threads
CountDownLatch
10 Tasks
Execution
Round 1
Thread 1 β Task 1
Thread 2 β Task 2
Thread 3 β Task 3
β
Round 2
Thread 1 β Task 4
Thread 2 β Task 5
Thread 3 β Task 6
β
Round 3
Thread 1 β Task 7
Thread 2 β Task 8
Thread 3 β Task 9
β
Round 4
Thread 1 β Task 10
Only three tasks run simultaneously because there are only three worker threads.
However, the latch waits for all ten tasks.
Latch Count
10
β
9
β
8
β
...
β
1
β
0
β
Main Thread Continues
Real-World Use Case 1: Processing 10,000 Orders
Suppose an e-commerce company receives 10,000 orders.
Creating 10,000 threads would waste memory.
Instead,
ExecutorService executor =
Executors.newFixedThreadPool(20);
CountDownLatch latch =
new CountDownLatch(10000);
Execution
20 Worker Threads
β
Process first 20 orders
β
Threads become free
β
Process next 20 orders
β
Repeat
β
10,000 Orders Completed
β
Latch becomes 0
β
Generate Final Report
Real-World Use Case 2: Sending Emails
5000 Emails
β
10 Threads
β
Send Emails
β
Every task
countDown()
β
Wait for all emails
β
Generate Success Report
Real-World Use Case 3: Salary Slip Generation
15000 Employees
β
25 Worker Threads
β
Generate Salary Slip
β
countDown()
β
All Completed
β
Create ZIP File
β
Notify Employees
What Happens If One Task Fails?
Suppose three services are running.
Product Service β
Order Service β
User Service β
Should the waiting thread continue?
Yes.
But only if countDown() is always executed.
Why countDown() Should Always Be Inside finally
Correct way
executor.submit(() -> {
try {
orderService.getOrders();
} catch (Exception e) {
System.out.println("Order Failed");
} finally {
latch.countDown();
}
});
Execution
Initial Count = 3
β
Product Success
Count = 2
β
Order Failed
finally executes
Count = 1
β
User Success
Count = 0
β
await() returns
Even though one task failed, the waiting thread is released.
What Happens If countDown() Is Not Inside finally?
Wrong implementation
executor.submit(() -> {
try {
orderService.getOrders();
latch.countDown();
} catch (Exception e) {
System.out.println("Failed");
}
});
Suppose the service throws an exception.
Execution
Initial Count = 3
β
Product Success
Count = 2
β
Order Failed
countDown() skipped
Count still = 2
β
User Success
Count = 1
β
await()
Waiting...
Waiting...
Waiting...
Forever
The latch never reaches zero.
The waiting thread never continues.
This is one of the most common mistakes developers make.
Best Practice
Always write your task like this.
executor.submit(() -> {
try {
// Business Logic
} catch (Exception e) {
// Handle Exception
} finally {
latch.countDown();
}
});
No matter what happens,
- Success
- Failure
- Exception
the latch is always decremented.
Common Interview Questions
1. Does CountDownLatch count have to equal the thread count?
No.
Thread count controls how many tasks execute simultaneously.
Latch count controls how many task completions to wait for.
2. Can one thread call countDown() multiple times?
Yes.
The latch only counts method calls.
It does not track which thread called them.
3. Can CountDownLatch be reused?
No.
Once the count reaches zero, it cannot be reset.
For reusable synchronization, use CyclicBarrier or Phaser.
4. What happens if countDown() is never called?
The waiting thread remains blocked forever unless you use a timed wait.
latch.await(5, TimeUnit.SECONDS);
5. Does countDown() block the thread?
No.
It simply decrements the count and returns immediately.
Only await() blocks.
Common Mistakes
β Setting latch count equal to thread count without considering the number of tasks.
β Forgetting to call countDown().
β Calling countDown() only after successful execution.
β Never shutting down the executor.
β Assuming CountDownLatch can be reused.
When Should You Use CountDownLatch?
Use it when you need to wait for multiple independent tasks to finish before continuing.
Examples include:
- Loading data from multiple microservices
- Processing thousands of orders
- Sending bulk emails
- Importing CSV files
- Parallel file processing
- Batch jobs
- Report generation
- Running initialization tasks during application startup
CountDownLatch at a Glance
| Feature | Description |
|---|---|
| Purpose | Wait for multiple tasks to finish |
| Blocks Thread | Yes (await()) |
| Decrements Count | countDown() |
| Reusable | β No |
| Thread-safe | β Yes |
| Commonly Used With | ExecutorService, thread pools, batch processing |
| Best Practice | Always call countDown() inside finally |
Conclusion
CountDownLatch is a simple yet powerful synchronization utility that helps coordinate multiple concurrent tasks. It is especially useful when a thread must wait for several independent operationsβsuch as API calls, file processing, or batch jobsβto finish before moving on.
Remember these key points:
await()blocks until the count reaches zero.countDown()decreases the count by one.- The latch count represents the number of task completions, not the number of threads.
- The thread pool size and latch count are independent.
- Always place
countDown()in afinallyblock to prevent deadlocks if a task throws an exception. - A
CountDownLatchis a one-time synchronization aid; once the count reaches zero, it cannot be reset.
Understanding these concepts will help you write reliable concurrent code and confidently answer CountDownLatch questions in Java interviews.