Tags: web-dev concept

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.all for things that don’t depend on each other; sequential await only where the second genuinely needs the first
  • Watch out for accidental sequencing — an await inside 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.all fails fast; Promise.allSettled doesn’t. Choose deliberately, because partial success is usually what you want when fetching independent things