Concurrency vs Parallelism
Dealing with many things at once versus doing many things at once — and why they're not the same.
The distinction that trips everyone up
These two words are used interchangeably in casual talk, but they mean genuinely different things, and mixing them up leads to bad design decisions. The cleanest way to hold them apart:
- Concurrency is dealing with many things at once. It is about structure — organizing a program so that multiple tasks are in progress and interleaved, making progress by taking turns. Crucially, concurrency is possible on a single CPU core, by rapidly switching between tasks so all of them advance.
- Parallelism is doing many things at once. It is about execution — literally running multiple tasks at the same instant, which requires multiple CPU cores actually working simultaneously.
Rob Pike, one of Go's designers, put it in a line worth memorizing: "Concurrency is not parallelism." Concurrency is a way to structure a program to handle many things; parallelism is running things at the same time. A concurrent program can run in parallel if you give it multiple cores, but it doesn't have to — the two ideas are independent.
An analogy: one barista taking three customers' orders, starting each drink and switching between them, is concurrent — one worker juggling tasks. Three baristas each making one drink is parallel — genuinely simultaneous. You can be concurrent without being parallel.
Single core versus multiple cores
The hardware picture makes the difference concrete. On one core, concurrency is an illusion of simultaneity created by fast switching; on many cores, parallelism is real simultaneity.
In the top box, one core time-slices between task A and task B — at any single instant only one runs, but over a second both advance. In the bottom box, two cores run A and B at the very same instant. Concurrency = interleaving on one core; parallelism = simultaneous on many cores. A concurrent program placed on a multi-core machine can become parallel, which is why the two so often appear together.
Processes, threads, and async
Programs achieve concurrency and parallelism through three units of execution, and knowing how they differ tells you what you're actually getting.
| Unit | Memory | Cost to create/switch | Gives you |
|---|---|---|---|
| Process | Isolated, own memory | Heavy | Parallelism with strong isolation |
| Thread | Shared within a process | Medium | Parallelism + easy sharing (and shared-state bugs) |
| Async / coroutine | Shared, one thread | Very light | Concurrency without extra threads |
- Processes are independent programs with their own memory. They can run truly in parallel across cores and are well isolated, but they are expensive and communicating between them is costly.
- Threads live inside a process and share its memory. Multiple threads can run in parallel on multiple cores, and sharing memory is easy — which is also the source of the bugs below.
- Async / coroutines are a single-threaded concurrency model: one thread cooperatively switches between many tasks whenever a task would otherwise wait. No extra threads, no parallelism — pure interleaving.
Context switching is the cost that ties these together: every time the system swaps which task a core is running — between threads or processes — it must save one task's state and load another's. Switches are not free, so a design that switches constantly can spend real time just switching.
CPU-bound versus IO-bound — the deciding question
The single most useful question when choosing an approach is: is the work CPU-bound or IO-bound? This determines whether you need parallelism or merely concurrency.
- CPU-bound work keeps the processor busy computing — image processing, encryption, number crunching. The bottleneck is raw compute, so the only way to go faster is more cores working at once: you need parallelism.
- IO-bound work spends most of its time waiting — for the network, disk, or a database to respond. The CPU sits idle during the wait. Here you don't need more cores; you need to not waste the wait. Concurrency lets one core start another task while the first is blocked, so a single thread can keep thousands of slow network calls in flight.
This is the practical heart of the day: match CPU-bound work to parallelism and IO-bound work to concurrency. A web server handling thousands of slow requests is IO-bound (concurrency wins); a video encoder is CPU-bound (parallelism wins).
The async event-loop model
Async concurrency deserves its own picture because it is how much modern IO-heavy software (Node.js, Python's asyncio, Go's runtime) achieves massive concurrency on few threads. The engine is an event loop: a single thread that runs one task until the task hits something it must wait for (a network reply), at which point the task yields control back to the loop, which immediately picks up another ready task. When the awaited result arrives, the loop resumes the first task.
Because the thread never sits idle waiting — it always switches to other ready work — one thread can juggle thousands of in-flight IO operations. The catch: if any single task does heavy CPU work without yielding, it blocks the whole loop and every other task stalls. Async is superb for IO-bound concurrency and poor for CPU-bound work, which needs true parallelism instead.
When to reach for which
| Situation | Reach for |
|---|---|
| Thousands of slow network/DB calls | Async concurrency (event loop) |
| Heavy computation to speed up | Parallelism (multiple processes/threads) |
| Mix of both | Parallel workers, each internally async |
| Simple isolation, crash safety | Separate processes |
Rule of thumb: if the task waits, use concurrency; if the task computes, use parallelism. Many real systems combine them — a pool of parallel worker processes, each running an async event loop to overlap its own IO.
Key takeaways
- Concurrency is dealing with many things at once (structure, interleaving, progress on multiple tasks — possible on one core); parallelism is doing many things at once (simultaneous execution on multiple cores).
- Rob Pike: "Concurrency is not parallelism" — a concurrent program can run in parallel given multiple cores, but doesn't have to.
- Processes are isolated and heavy, threads share memory and can run in parallel, async/coroutines give concurrency on a single thread; context switching is the cost of swapping tasks.
- The deciding question is CPU-bound (needs parallelism) versus IO-bound (benefits from concurrency).
- Shared memory brings race conditions; locks/mutexes serialize access; locks acquired in conflicting orders cause deadlock.
- The async event-loop model gives huge single-threaded concurrency for IO-bound work but stalls on CPU-heavy tasks.
- If the task waits, use concurrency; if it computes, use parallelism — and real systems often combine both.
Checklist
- [ ] I can state the difference between concurrency and parallelism in one sentence each.
- [ ] I can explain why concurrency works on a single core and parallelism needs multiple cores.
- [ ] I can distinguish processes, threads, and async/coroutines and what each provides.
- [ ] I can define CPU-bound vs IO-bound and pick the right approach for each.
- [ ] I can explain a race condition and how a lock/mutex prevents it.
- [ ] I can describe how a deadlock arises and one way to avoid it.
- [ ] I can explain the async event-loop model and why it suits IO-bound but not CPU-bound work.