32

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.

UnitMemoryCost to create/switchGives you
ProcessIsolated, own memoryHeavyParallelism with strong isolation
ThreadShared within a processMediumParallelism + easy sharing (and shared-state bugs)
Async / coroutineShared, one threadVery lightConcurrency 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).

Shared state — races, locks, and deadlock

The moment two threads share memory, a new class of bug appears. A race condition happens when two threads read and write the same data at the same time and the final result depends on who happened to go first. Classic example: two threads both read a counter as 5, both add 1, both write 6 — one increment is silently lost.

The standard fix is a lock (or mutex, for "mutual exclusion"): a gate that only one thread may hold at a time. A thread acquires the lock before touching the shared data and releases it after, so the read-modify-write happens as an uninterrupted unit and no update is lost.

Locks introduce their own hazard: deadlock. If thread 1 holds lock A and waits for lock B, while thread 2 holds lock B and waits for lock A, neither can ever proceed — both are stuck forever, each waiting on the other. Avoiding deadlock (for instance, always acquiring locks in the same global order) is a core discipline of multi-threaded programming. This whole category of pain is exactly what the single-threaded async model sidesteps: with one thread there is no shared-memory race to guard against.

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

SituationReach for
Thousands of slow network/DB callsAsync concurrency (event loop)
Heavy computation to speed upParallelism (multiple processes/threads)
Mix of bothParallel workers, each internally async
Simple isolation, crash safetySeparate 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.