Synchronous vs asynchronous
Learn why blocking JavaScript freezes the page, how browser and Node host APIs wait outside the call stack, and why callbacks run later without interrupting current code.
- 01Why blocking freezes the pageConnect long synchronous work to an unresponsive main thread.
- 02What the host does for JavaScriptDescribe timers, network, and file APIs as work the host waits on.
- 03Why callbacks run laterPredict callback order with run-to-completion rules.
The central idea
JavaScript often has one main thread for your page: one place where it runs your code, updates the page, and handles clicks. If that thread is busy, the page feels busy too. The big idea of this lesson is simple: synchronous code makes JavaScript wait right now; asynchronous code lets a host wait somewhere else and call you back later.
You already met hosts in Engines, runtimes & hosts, where a long main-thread job froze the page, and the queue overview in The JavaScript runtime at a glance. This lesson connects those pieces to everyday code: timers, click handlers, network responses, and callbacks from Function expressions & callbacks.
Imagine calling a bakery and waiting silently on the line until the answer arrives. That is synchronous waiting. Now imagine sending a text and continuing your day. When the reply arrives, you handle it then. That is asynchronous waiting.
- In real life: Phone call: you stay on the line
- In JavaScript: Synchronous work: the next line waits
- In real life: Text message: send it and continue your day
- In JavaScript: Asynchronous request: host waits while JS continues
- In real life: Reply arrives later
- In JavaScript: Callback runs later
Where the analogy stops: Texts can arrive while you are doing anything. JavaScript still runs one task at a time: the reply waits until the current code finishes.
Synchronous code runs now, to completion, before the next task starts. Asynchronous code starts something now, lets the host wait, and runs your callback later.
Blocking the main thread
INTERACTIVEBlocking means JavaScript keeps the main thread so busy that the browser cannot paint, scroll smoothly, or respond to input. A long calculation, a huge JSON.parse, or a deliberate busy loop all have the same shape: the engine is working now, so everything else waits.
The experiment below uses a kitchen story. A synchronous “bake” keeps checking the oven in JavaScript for just under a second. An asynchronous bake asks the host timer to wait and leaves JavaScript free. This is capped like the earlier engine lesson’s freeze demo, but framed around waiting for a response rather than raw engine work.
function bakeSynchronously() { const started = performance.now(); while (performance.now() - started < 1000) { // JS is busy here, so the page cannot repaint. } console.log("sync bake done");} function bakeAsynchronously() { setTimeout(() => { console.log("async bake done"); }, 1000);}The counter tries to update ten times per second. If the main thread is blocked, the number stops changing.
- Counter is alive. Try the two bake buttons.
Run the synchronous bake, then reset and run the asynchronous bake. Watch whether the counter moves.
When the busy loop runs, even the little counter cannot repaint. The browser may record your click or key press, but it cannot run the next handler until JavaScript gives the thread back. This is why production code avoids “sleep” loops on the main thread.
Host APIs do the waiting
INTERACTIVEThe JavaScript language does not include a timer, a browser window, a file system, or a network. Browsers and Node provide those as host APIs. When you call setTimeout, start a network request, or read a file in Node, JavaScript hands work to the host. The callback is saved, not run yet.
A good assistant can wait on hold, watch a timer, or check the door while you keep working. The host plays that role for JavaScript. It tracks outside waiting and leaves a callback note when the result is ready.
- In real life: You ask an assistant to watch the oven
- In JavaScript: JS calls a host API like
setTimeout - In real life: You keep chopping vegetables
- In JavaScript: The current script keeps running
- In real life: The assistant puts a note on your desk
- In JavaScript: The callback is queued when ready
Where the analogy stops: The assistant does not run your JavaScript for you. JavaScript reads the note only when the current task is finished.
console.log("1. JS starts the recipe");setTimeout(() => { console.log("3. host puts the callback back");}, 1000);console.log("2. JS keeps cooking");Press Start. JavaScript will start the timer and keep going while the host counts.
This is why asynchronous code can keep an interface responsive. The waiting happened outside the JavaScript stack. A timer callback, a click handler, and a response handler are all notes that come back later. Promises, coming in Module 22, are a structured way to work with that same “later result” idea.
Callbacks run later
STEP THROUGHA callback is a function you pass to other code. Some callbacks run immediately: array.map calls its callback while map is running. Host callbacks run later: timers, event listeners, and response handlers wait until the host has something to report.
The classic test is setTimeout(..., 0). A delay of zero does not mean “interrupt immediately.” It means “ask the host to queue this callback as soon as it can, after the current JavaScript finishes.”
Predict the console order. The timer delay is 0, but the callback still runs after the current script finishes.
script
setTimeout(() => { console.log("B: timer callback");}, 0);console.log("C: end");setTimeout(callback, 0) still crosses the host boundary. The callback becomes a later task. That is why A prints, then C, then B.
console.log("ready")setTimeout(() => console.log("ding"), 0)- a
fetchresponse handler - the callback passed to
array.map button.addEventListener("click", handler)JSON.parse("{\"ok\":true}")
Sort each callback or operation by whether it runs during the current turn or later after a host event.
Run to completion
STEP THROUGHRun to completion means JavaScript finishes the task in its hands before picking up another one. A timer can become ready while a loop is running, but it cannot cut in. This rule makes code easier to reason about: local variables do not change halfway through a statement because a callback interrupted them.
The timer is ready quickly, but run-to-completion means the loop in your hand finishes before the note is picked up.
script
console.log("timer callback");}, 0);console.log("loop starts");for (let i = 0; i < 3; i = i + 1) { console.log("loop " + i);}console.log("loop ends");| Question | Synchronous | Asynchronous |
|---|---|---|
| Who waits? | JavaScript waits on the main thread | The host waits outside JavaScript |
| Can the page respond meanwhile? | No, not while JS is busy | Usually yes, because JS is free between callbacks |
| When does the callback run? | There may be no callback; work happens now | Later, after the current task completes |
| Examples | console.log, JSON.parse, array.map | setTimeout, click handlers, response handlers |
This also explains old browser dialogs like alert and prompt. They pause the page while the dialog is open. The Talking to the user lesson covers their user-facing behavior; for this lesson, remember that they are blocking APIs and should be used carefully.
Where you’ll use this
The sync/async split shows up whenever code waits for something: a debounce timer in a search box, a loading spinner while data comes back, a progress indicator during a file upload, or a “Save” button that stays usable while the network request is in flight.
Do quick CPU work synchronously. Hand real waiting to a host API. If CPU work itself is huge, break it into chunks or move it to a worker so the main thread can breathe.
For example, validating one field can happen now. Waiting for an availability check should be asynchronous. Rendering a small template can happen now. Parsing a giant response or filtering a huge list may need chunking so it does not block input.
Common misconceptions
“Asynchronous means parallel JavaScript.”
Not here. The host may wait in parallel, but your callbacks still run one at a time on the main JavaScript thread.
“A zero timer runs immediately.”
It runs later. The current script and any already-running callback finish first.
“Every callback is asynchronous.”
array.map and forEach call their callbacks synchronously. The source of the callback matters.
“Async code can fix slow CPU work by itself.”
Waiting on a host can be async. CPU-heavy JavaScript still blocks unless you split it up or move it off the main thread.
“The host can interrupt my function whenever it wants.”
Run-to-completion prevents that. A ready callback waits until the current task returns.
Practice: sync, async, and later callbacks
5 EXERCISESPredict the console output order without running it, then check your answer.
console.log("A: start");
setTimeout(() => {
console.log("B: timer callback");
}, 0);
console.log("C: end");The real order is A: start, then C: end, then B: timer callback. The host receives the timer request, but JavaScript finishes the current script before it runs the callback.
Replace the busy loop with a host API so the page can remain responsive. Which API should you reach for?
function waitOneSecond() {
const started = Date.now();
while (Date.now() - started < 1000) {}
console.log("done");
}function waitOneSecond() {
setTimeout(() => {
console.log("done");
}, 1000);
}setTimeout asks the host to wait for 1000 ms. JavaScript can return to the page, then the callback logs done later.
Explain whether alert and prompt block the main thread.
Yes. alert and prompt are blocking browser APIs. While the dialog is open, the page is paused, which is why modern interfaces avoid them for ordinary UI.
Name two callbacks from this lesson that run later instead of during the current line of code.
Examples include a setTimeout callback, an event listener callback, and a response handler. array.map is not one: it calls its callback synchronously while mapping.
Can the timer callback interrupt the loop? Answer yes or no, then explain why.
setTimeout(() => {
console.log("timer callback");
}, 0);
console.log("loop starts");
for (let i = 0; i < 3; i = i + 1) {
console.log("loop " + i);
}
console.log("loop ends");No. The timer callback cannot interrupt the loop. JavaScript prints loop starts, each loop line, and loop ends before it prints timer callback.
Quiz: check your understanding
7 QUESTIONSRead each explanation, especially when a wrong answer almost sounded right.
Question 1 of 7What does synchronous code mean?
Choose an answer to see the explanation.
Question 2 of 7What does this print?
Read the code, then predictconsole.log("A"); setTimeout(() => console.log("B"), 0); console.log("C");Choose an answer to see the explanation.
Question 3 of 7Who does the waiting for
setTimeout?Choose an answer to see the explanation.
Question 4 of 7Which callback usually runs during the current synchronous turn?
Choose an answer to see the explanation.
Question 5 of 7What does run to completion mean?
Choose an answer to see the explanation.
Question 6 of 7Why can
JSON.parsefreeze a page on huge input?Choose an answer to see the explanation.
Question 7 of 7Which statement is the safest rule?
Choose an answer to see the explanation.
Key takeaways
- Synchronous code runs now and blocks the next JavaScript work until it finishes.
- Blocking the main thread freezes rendering and event handling because the page shares that thread.
- Host APIs such as timers, events, network, and file reads do the waiting outside JavaScript.
- Callbacks from host APIs run later; callbacks passed to synchronous methods like
maprun now. - Run to completion means a ready callback waits until the current script or callback finishes.
Remember the one-liner.
Synchronous code keeps you on the phone; asynchronous code sends a text, lets the host wait, and handles the reply later.
Up next: Timers: setTimeout & setInterval.