Free lesson · JavaScript Basic to Advance
Why JavaScript Is Single-Threaded
One Thread, Many Things to Do
JavaScript runs on a single thread. There's one line of execution. One thing happens at a time. If your code is busy doing something, nothing else can happen until that thing finishes.
console.log("Step 1");
console.log("Step 2");
console.log("Step 3");
Three logs, executed in order, one after the other. Nothing surprising. JavaScript runs them on its single thread, top to bottom, and finishes when the script ends.
But now consider what should happen when JavaScript needs to wait for something slow — a network request, a database query, a file read, a user clicking a button. If JavaScript truly only does one thing at a time, and that one thing might take seconds or minutes, then a single slow operation would freeze everything. The browser tab would lock up. The Node server would stop responding to requests. Every other piece of work would be blocked.
That's not what actually happens. JavaScript handles waiting elegantly without blocking. This module is about how it does that — and why understanding the mechanism matters more for interview prep than almost any other JavaScript topic.
I'm Emeka. I work at Netflix on the Data Platform, handling real-time event streaming at scale — Kafka, Flink, billions of events per day. Async programming isn't an abstract topic for me; it's the model I live in every day. JavaScript's async model is one of the cleanest in the industry, and once you understand it, the language stops surprising you.
Module 9 is the longest module in the course. Twelve Topics. Async is also the most-asked JavaScript interview topic after closures. Spend time here.
Interactive animation: One thread runs each handler to completion before the next - no locks or races, but one slow handler blocks everything.
What "Single-Threaded" Actually Means
A thread is an independent line of execution. A program with multiple threads can do multiple things genuinely simultaneously — one thread parses input while another draws to the screen while another saves to disk. Multi-threaded programming is powerful but complicated. Threads can interfere with each other, race for resources, and produce hard-to-reproduce bugs.
JavaScript was designed to be single-threaded specifically to avoid this complexity. There's one thread for executing your code. One call stack. One sequence of operations. If something is running, nothing else runs alongside it.
function takeAWhile() {
// simulate slow work — busy loop
const end = Date.now() + 100;
while (Date.now() < end) { /* spinning */ }
return "done";
}
console.log("before");
console.log(takeAWhile());
console.log("after");
The whole script runs on one thread. While takeAWhile is spinning, nothing else can happen — the call stack has one item on it (the function call) and JavaScript can't do anything else until that item finishes.
In a browser, this kind of busy work would freeze the page. Animations would stop. Clicks would be ignored. The tab would appear hung. In a Node server, incoming requests would queue up while one slow request blocks. This is why busy loops are a serious problem in JavaScript and almost never appear in real code.
Interactive animation: A click handler that spins for five seconds - clicks, animations and the whole page freeze until it returns.
The Single-Threaded Constraint
The single-threaded design creates a specific constraint: anything that takes meaningful time must not run on the main thread synchronously. Network requests, file I/O, timers, user input — all of these have to happen somewhere else and then notify JavaScript when they're done.
That "somewhere else" is the runtime environment around JavaScript. In a browser, it's the browser itself, which has many threads for various things — networking, rendering, file system, etc. In Node.js, it's libuv, a C library that gives Node access to the operating system's async I/O capabilities.
graph TD
A[Your JavaScript code] --> B[Single-threaded JS engine]
B --> C[Web APIs / Node APIs<br/>networking, timers, file I/O<br/>not part of the JS engine]
style B fill:#7c3aed,color:#fff
style C fill:#10b981,color:#fff
This diagram shows JavaScript's architecture. Your code runs on a single-threaded JavaScript engine — one thread, one call stack. But the engine has access to APIs that live outside the engine itself — web APIs in the browser, Node APIs in Node.js — that handle things like networking, timers, and file I/O. Those APIs can do work without blocking JavaScript, and when their work is done, they notify the engine that the result is ready.
This is the architectural trick. JavaScript itself stays single-threaded — simple, no race conditions, no thread synchronization. But the environment around it is multi-threaded, capable of doing slow work without blocking the main JavaScript thread.
The bridge between the two is what we cover next: the event loop, which is how the environment delivers completed work back to JavaScript for processing.
The Async Pattern
Whenever JavaScript starts something that might take time, it doesn't wait. It hands the work off to the environment and continues executing.
console.log("start");
setTimeout(() => {
console.log("delayed");
}, 1000);
console.log("end");
The output: start, then end immediately, then delayed after about a second. JavaScript doesn't wait at the setTimeout line. It hands the timer to the environment and continues. A second later, the environment notifies JavaScript that the timer is done, and the callback runs.
This is the async pattern. You schedule work. You provide a callback. You move on. Later, when the work is done, the callback runs. JavaScript's single-threaded nature is preserved — the main code runs to completion, and the callback only runs after.
In this Topic, the work is just a timer. But the same pattern applies to everything async — network requests, file reads, user interactions, database queries. All of them schedule work outside the main thread and notify JavaScript when done.
Interactive animation: A zero-delay timer still logs last - the current run finishes before the callback gets the stack.
Why Single-Threaded Wins for Web Apps
Interactive animation: The road not taken - threads that race and need locks, against one thread that runs each function to completion.
You might wonder why JavaScript wasn't built multi-threaded. After all, multi-threaded languages can do real parallelism. Wouldn't that be more powerful?
For the workloads JavaScript actually does, no. Most JavaScript code is I/O-bound, not CPU-bound. The work isn't doing heavy computation — it's waiting on network responses, reading files, handling user input. For I/O-bound work, single-threaded async is roughly as fast as multi-threaded, and dramatically simpler to reason about.
At Netflix, we process billions of events per day through Node.js servers. The single-threaded model handles this volume because the work is I/O-bound — the events come from Kafka over the network, get processed briefly, and get written back over the network. The actual CPU work is small. Single-threaded JavaScript handles it cleanly.
For CPU-bound work (heavy computation, image processing, video encoding), single-threaded JavaScript is the wrong tool. That's what Web Workers (in browsers) and Worker Threads (in Node) are for — they spawn separate threads for genuinely parallel computation. But those are exceptions. The default model is single-threaded, and for most web workloads, it's the right choice.
What This Module Will Build
The rest of Module 9 builds up your understanding of how single-threaded async actually works:
- Topic 9.2 — the event loop, the mechanism that delivers completed async work back to JavaScript
- Topics 9.3–9.4 — callbacks and Promises, the two main patterns for handling async results
- Topics 9.5, 9.7 — error handling in async code
- Topic 9.6 — async/await, the modern syntax that makes async code read like sync code
- Topic 9.8 — composing multiple async operations
- Topic 9.9 — the microtask queue, the deeper details of the event loop
- Topic 9.10 — async iteration
By the end of Module 9, you should be able to read any async JavaScript code, reason about when each piece runs, and predict the output of timing-sensitive examples. That's the depth interviewers test.
Common Pitfalls
Warning: The single-threaded model traps.
Blocking the main thread. Long computations, busy loops, and synchronous I/O all block JavaScript. In a browser, this freezes the page. In Node, it blocks all other requests. Be deliberate about CPU-heavy work — break it up, use Web Workers, or move it off the main thread.
Assuming async means parallel. It doesn't. Async means "don't wait." The actual work still happens single-threaded in JavaScript itself. The parallelism is in the environment, not in JavaScript.
Mixing up "scheduled" with "running now".
setTimeout(fn, 0)doesn't runfnimmediately. It schedulesfnto run after the current code finishes. We'll cover this carefully in Topic 9.2.
Why This Matters in Interviews
Interview: Async questions are almost guaranteed in JavaScript interviews. The foundation — "JavaScript is single-threaded" — is the warm-up. The follow-up is always about how the language handles waiting without blocking.
A candidate who can explain single-threaded JavaScript at the level of "main thread does code, environment does async work, event loop bridges them" has signaled solid mental model. A candidate who can't articulate this signals fragility in everything async that follows.
Topic 9.11 — Interview Lens: The Event Loop Question — covers the deeper treatment of this material.
Try It
Exercise: Predict what this code logs, in order, before running it:
console.log("1"); setTimeout(() => { console.log("2"); }, 0); console.log("3"); setTimeout(() => { console.log("4"); }, 0); console.log("5");Notice the second and fourth
setTimeoutcalls both have delay0. That doesn't mean they run immediately — it means they run "as soon as possible after the current code finishes." What's the actual output order? And why isn't it 1, 2, 3, 4, 5?The answer reveals the basic shape of JavaScript's async model. Once you can predict this, the next Topic on the event loop will make sense.