The Single-Threaded Model
Introduction
JavaScript runs your code on a single thread with one call stack. Only one statement executes at any instant. Yet apps feel concurrent — they fetch data, animate, and respond to clicks "at the same time".
The resolution to this apparent paradox is non-blocking, asynchronous I/O: the single thread offloads slow work to the host and keeps moving.
Why This Matters
This model explains why a long synchronous loop freezes the whole UI, why setTimeout(fn, 0) still runs after your code, and why CPU-heavy work belongs in a Web Worker. It is one of the most common senior-level discussion topics.
Theory
Single thread, one call stack
Every function call pushes a frame; every return pops one. Because there is only one stack, JavaScript cannot run two functions truly simultaneously. "Run-to-completion" applies: a function runs fully before anything else can.
Blocking is the enemy
If a function takes a long time (a huge loop, synchronous file read, alert), the single thread is blocked — no other code, events, or rendering can happen until it returns. In a browser this looks like a frozen page.
How concurrency is achieved anyway
Slow operations are handed to the host (Web APIs / libuv), which may use its own threads under the hood. When the work finishes, a callback is queued and the event loop runs it once the stack is clear. The JS you write stays single-threaded; the environment provides the concurrency.
True parallelism: Web Workers
For CPU-bound work, browsers offer Web Workers (and Node offers worker threads): separate threads with their own call stack and memory, communicating via message passing. They don't share the main thread's variables, which keeps the model race-free by default.
Visual Diagrams
Time --->
main() [==================================]
push a() [====]
push b() [==]
(b returns) pop
(a returns) pop
(main returns) pop
Nothing else runs until the stack is empty. A long frame blocks everything.One call stack means one thing at a time; long tasks block the whole app.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1console.log('one');2setTimeout(() => console.log('two'), 0);3console.log('three');Coding Exercises
Don't block the thread: chunk the work
MediumYou must sum the numbers 0..N where N is very large, but you must not freeze the UI. Write a function sumInChunks(n, chunkSize, done) that processes the sum in small chunks, yielding to the event loop between chunks, and calls done(total) when finished.
Interview Questions
1If JavaScript is single-threaded, how can it do things concurrently?
The JavaScript code runs on one thread with one call stack, but the environment provides concurrency. Slow operations (timers, network, file I/O) are delegated to host Web APIs/libuv, which may use OS threads. When they complete, a callback is queued and the event loop runs it once the stack is empty. So concurrency comes from non-blocking I/O and the event loop, not from multiple JS threads.
Follow-ups
- What blocks the main thread and how do you avoid it?
- When would you reach for a Web Worker?
2What does it mean that JavaScript has 'run-to-completion' semantics?
Once a function starts, it runs fully before any other queued task or callback can execute — the event loop won't interrupt it. This makes reasoning about state easier (no pre-emption mid-function) but means a long-running function blocks everything, including rendering and events.
Quiz
1. Why does a long synchronous for-loop freeze the browser UI?
2. Where should heavy CPU-bound work go to keep the UI responsive?
Summary
- JavaScript executes your code on one thread with a single call stack.
- Run-to-completion: a function finishes before anything else runs.
- Long synchronous work blocks events and rendering — keep tasks short.
- Concurrency comes from non-blocking host APIs + the event loop; parallelism from Web Workers.
Cheat Sheet
Model: one thread, one call stack, run-to-completion.
Blocking: long sync work freezes UI (no events, no repaint).
Concurrency: host APIs do slow work off-thread → queue callbacks → event loop.
Parallelism: Web Workers / worker threads (own stack + memory, message passing).