How to know if other threads have finished?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Knowing whether other threads have finished is a synchronization problem, not something you should solve with ad hoc polling flags. The right answer depends on your concurrency model, but the standard tools are join, futures, latches, or other blocking coordination primitives rather than busy waiting.
Core Sections
join is the simplest tool for a fixed set of threads
If you already have explicit thread objects, join() is usually the clearest answer. It blocks until the target thread finishes.
This is the normal answer when you control the set of worker threads directly.
Futures are better when you want task results too
If the work is submitted through an executor, waiting on futures is often better than managing thread objects yourself.
This solves two problems at once:
- you know when the task is finished
- you can retrieve its result or exception cleanly
That is usually better than inventing your own completion flags.
Timeouts matter in real systems
Waiting forever is dangerous in services, daemons, and interactive applications. Use timeouts when an indefinite wait would be operationally risky.
A timeout does not tell you the task finished. It tells you that the wait exceeded a budget. That distinction is important when designing shutdown and recovery logic.
Java examples: join, Future, and CountDownLatch
In Java, the same coordination ideas exist under different APIs.
If you have a fixed number of workers and one coordinator that should continue only after all of them finish, CountDownLatch is also a strong option.
This is often cleaner than manually tracking several booleans.
Avoid busy waiting
This kind of loop is usually a bad sign:
Or even:
These approaches waste CPU or introduce unreliable timing behavior. More importantly, they often avoid the real issue: there should be a proper synchronization primitive expressing the handoff between threads.
Completion and success are different questions
A thread finishing does not mean it finished successfully. Good waiting logic also considers exceptions or failure paths.
With futures, exceptions are re-raised when you call result(). That is a major reason futures are often better than raw thread state checks.
Common Pitfalls
- Polling a shared flag instead of using
join, futures, or latches wastes CPU and often hides synchronization bugs. - Waiting forever with no timeout can make shutdown and failure recovery harder in production systems.
- Treating completion as success ignores exceptions raised by worker code.
- Mixing manual thread management with executor-style task management without a clear ownership model makes lifecycle control harder.
- Using a primitive boolean without proper synchronization in some languages can create visibility issues between threads.
Summary
- Use
join()when you have a fixed set of thread objects and just need to wait for them to finish. - Use futures when you also care about results, exceptions, and flexible task coordination.
- Use latches or similar primitives when one coordinator needs to wait for several workers.
- Avoid busy-wait loops and ad hoc polling flags.
- Always separate the question “has it finished?” from the question “did it succeed?”

