Concurrency and Parallelism
Date: 2026-08-17
Concurrency is dealing with many things at once; parallelism is doing many things at once. The distinction matters because one thread can be highly concurrent and not parallel at all — which is exactly what JavaScript is.
Concurrency is a program’s ability to have multiple tasks in progress, interleaved. Parallelism is executing multiple tasks simultaneously on separate processors.
CONCURRENT, NOT PARALLEL one worker
A──┐ ┌──A──┐ ┌──A
└─B─┘ └─B─┘
tasks interleave; one runs at a time
PARALLEL two workers
A────────────────────
B────────────────────
genuinely simultaneous
Concurrency is about structure. Parallelism is about execution. A single-threaded runtime can be extremely concurrent — it just never runs two things at the same instant.
Why one thread is enough for most web work
Most of what an application does is waiting, not computing:
network request ~100ms waiting
disk / DB read ~10ms waiting
JSON parse ~1ms working
render ~5ms working
Parallelism doesn’t help you wait faster. Concurrency does — start all the waiting at once and handle each result as it lands. This is why an event loop with non-blocking I/O serves thousands of connections on one thread, and why adding threads to an I/O-bound program buys almost nothing — Async Models, The Event Loop.
// sequential — total 300ms
await a() // 100ms
await b() // 100ms
await c() // 100ms
// concurrent — total ~100ms
await Promise.all([a(), b(), c()])Same thread, same code, three times faster — because the waiting overlaps. This is the single most common easy win in application code.
Where parallelism is the answer
When the work is CPU-bound — actually computing, not waiting:
image processing · large sorts ·
compression · cryptography ·
parsing very large files
On one thread, CPU-bound work blocks everything, including rendering. In a browser the tool is a Web Worker — a genuinely separate thread with its own memory, communicating by message passing.
MAIN THREAD WORKER
UI, DOM, events ←→ heavy computation
must stay free no DOM access
messages copied,
not shared
No shared memory means no data races — the cost is copying data across the boundary, which for large payloads is itself expensive.
The hazards, and why JavaScript avoids most of them
Genuine parallelism introduces problems single-threaded code never has:
DATA RACE two threads write the same
memory, result depends on
timing
DEADLOCK each holds what the other
needs; both wait forever
STARVATION one task never gets scheduled
JavaScript’s single thread eliminates data races by construction. You still get Race Conditions — two async operations resolving in an unexpected order — but never two writes to the same variable at the same instant. That’s a substantially smaller problem, and it’s a large part of why the model is popular.
Server-side shapes
THREAD PER REQUEST Java, .NET, Rails
simple to reason about
memory per thread, context switching
blocking I/O is fine
EVENT LOOP Node, nginx
one thread, non-blocking I/O
huge connection counts, low memory
one CPU-bound task blocks everything
PROCESS PER CORE PHP-FPM, Gunicorn
isolation, no shared state
memory per process
Each is a different answer to the same question: what happens to the other requests while this one waits? — Reference - PHP.
Practical rules
- Overlap independent waits.
Promise.allfor things that don’t depend on each other; sequentialawaitonly where the second genuinely needs the first - Watch out for accidental sequencing — an
awaitinside a loop is n sequential round trips, and it’s the async equivalent of N+1 Queries - Move CPU-bound work off the main thread in the browser. Anything over ~50ms is a visible stall
- Concurrency limits matter. Firing 500 parallel requests is a self-inflicted denial of service on your own API — batch them
Promise.allfails fast;Promise.allSettleddoesn’t. Choose deliberately, because partial success is usually what you want when fetching independent things