Executing tasks in parallel
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Executing tasks in parallel means allowing multiple units of work to make progress at the same time. It can reduce total runtime significantly, but only when the work is independent enough and the coordination overhead is justified. The practical question is not whether parallelism is possible, but whether it actually improves the workload you have.
Concurrency Versus Parallelism
These terms are related but not identical.
- concurrency means tasks overlap in progress
- parallelism means tasks literally run at the same time on separate workers or cores
That distinction matters because I/O-heavy work and CPU-heavy work benefit from different tools. Waiting on network responses is very different from numerically heavy computation.
Parallel I/O Work With Threads
When tasks spend most of their time waiting on I/O, threads are often enough. The waits can overlap even if little CPU work is happening.
This finishes much faster than three sequential one-second waits.
CPU-Bound Work Needs a Different Strategy
For CPU-heavy work in Python, processes are often better than threads because of the GIL.
Processes can use multiple CPU cores instead of contending inside one interpreter thread.
Ordered Results Versus Completion Order
Some parallel APIs preserve submission order, while others give you tasks as soon as they finish. Choose intentionally.
Ordered with map:
Completion order with as_completed:
If you need results in original order, use APIs that preserve it.
Handle Errors Explicitly
Parallel failures are easy to miss if you do not inspect worker results.
Without explicit exception handling, one failed worker can quietly poison the whole run.
When Parallel Execution Is Not Worth It
Parallelism adds overhead:
- worker startup cost
- synchronization complexity
- memory duplication
- harder debugging and testing
If each task is tiny, the overhead may exceed the benefit. Measure before you parallelize.
Design Guidelines That Hold Up
A practical rule set:
- use threads or async tools for I/O-bound workloads
- use processes for heavy CPU-bound work in Python
- avoid shared mutable state across workers
- benchmark with realistic data sizes
These rules are more useful than generic advice to make everything parallel.
Common Pitfalls
One common mistake is using Python threads for CPU-heavy work and expecting linear speedup. Another is assuming results arrive in input order when using completion-based APIs. Developers also skip exception handling and lose visibility into failed tasks. Shared mutable state can introduce race conditions that are far worse than sequential slowness. Finally, many microbenchmarks exaggerate speedup because they do not reflect real workloads.
Summary
- Parallel execution helps when tasks are independent and large enough to justify overhead.
- Use threads for I/O-bound work and processes for CPU-bound Python work.
- Choose ordered or completion-based result handling deliberately.
- Always collect exceptions from worker tasks.
- Avoid shared mutable state unless synchronization is designed carefully.
- Measure real speedup before committing to a parallel architecture.

