Topic 10.1
Synchronous vs Asynchronous Code
In one line
Synchronous code runs one line after another and each line must finish before the next starts; asynchronous code starts a slow job, carries on, and handles the result later, so the page never freezes while it waits.
Think of it like this
Ordering at a café. In a synchronous café, the cashier takes your order, walks to the machine, makes your coffee, hands it over, and only then serves the next person: the whole queue stands still. In an asynchronous café, the cashier takes your order, gives you a token number and serves the next person while the machine works. When your coffee is ready, your number is called. Same cashier, far less waiting.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Synchronous
- Runs in order: each step must finish before the next one starts.
- Asynchronous
- Starts a job now and handles its result later, without stopping to wait.
- Thread
- One worker following one list of instructions. A page's JavaScript runs on one main thread.
- Main thread
- The browser thread that runs your page's JavaScript, handles input and draws the page.
- Blocking
- Keeping the thread busy so nothing else (clicks, drawing, timers) can happen.
- Long task
- Any piece of work that keeps the main thread busy for more than 50 milliseconds.
- Callback
- A function you hand to someone else to call later, when something has happened.
- Web Worker
- A separate background thread that can run JavaScript without freezing the page, but can't touch the DOM.
Step by step
01Synchronous code: one line at a time
The engine runs each statement to the end before starting the next. If one line takes a long time, every line after it waits.
console.log("Take order");
makeCoffee(); // suppose this takes 3 seconds of busy work
console.log("Serve next customer"); // waits 3 seconds02What blocking does to a page
While makeCoffee() runs, the main thread is busy. A click on a button is noted by the browser but its listener can't run. The screen can't repaint, so a spinner freezes mid-turn.
When the busy work ends, all the waiting clicks are handled at once, which feels broken to the user.
03Asynchronous code: start, carry on, finish later
setTimeout asks the browser to run a function after a delay. The browser keeps the timer; JavaScript moves straight to the next line.
console.log("Take order");
setTimeout(() => console.log("Coffee ready"), 3000);
console.log("Serve next customer"); // runs at once
// Take order
// Serve next customer
// (3 seconds later) Coffee ready04Who does the waiting?
The browser does. Timers, network requests and disk reads are handled by parts of the browser outside your JavaScript thread. When one finishes, the browser puts your callback in a queue, and the event loop runs it when the thread is free.
05Async can't fix heavy computing
Wrapping a two-second loop in setTimeout only moves the freeze to later. Real fixes: do less work, split it into small chunks so the browser can breathe between them, or move it to a Web Worker.
Try it yourself
- 1
Change the loop length
In the first example, change
300in the loop to100and the check towaited >= 100. Predict the order of the three lines first: does it change? - 2
Swap the timer delays
In the second example, make the toast take
400and the coffee100. Predict which line prints third, then run it. - 3
Feel the freeze
In the third example, press Freeze and then click the counter three times during the freeze. What does the counter show while frozen? What happens right after?
Code & diagrams
The timer asked for 0 ms, but it couldn't run until the loop gave the thread back. A timer's delay is a **minimum**, never a promise.
Preview
Console
Nothing logged yet.Expected console output
1. Start the timer (0 ms) and the slow work
2. Slow work finished
3. Timer finally ran. Waited at least 300 ms? trueBoth sync lines run first. The callbacks run later, in order of **when their timers finish**, not the order they were written.
Preview
Console
Nothing logged yet.Expected console output
Order a coffee
Read the newspaper while waiting
Toast is ready
Coffee is readyDuring the freeze nothing changes on screen. When it ends, all your counter clicks are handled at once.
Preview
Console
Nothing logged yet.Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Wait with a loop instead of a timer
To show a message after 3 seconds, write const s = Date.now(); while (Date.now() - s < 3000) {} and then show it.
Break #2
Expect async code to run in order
Write let data; setTimeout(() => { data = "ready"; }, 100); console.log(data);.
Myth vs fact
Myth
Async code runs at the same time as your other code, on another thread.
Fact
Your callbacks run on the same single main thread, one at a time. Only the waiting (timers, network) happens elsewhere, in the browser.
Myth
Making a function async makes heavy work faster.
Fact
Async only stops waiting from blocking. A long calculation blocks the thread wherever it runs; split it into chunks or use a Web Worker.
Myth
JavaScript is slow because it is single-threaded.
Fact
One thread plus non-blocking waiting handles thousands of open network requests very well; Node.js servers are built on it. Problems come only from long CPU work on the main thread.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Chrome's DevTools Performance panel marks tasks over 50 ms with a red corner as long tasks. The Interaction to Next Paint (INP) metric (a Core Web Vital since March 2024) measures how long the page takes to respond to clicks and key presses, and long tasks are its main cause.
- ▸
To keep a long job responsive, process it in chunks and yield to the browser between chunks, for example with
await new Promise((r) => setTimeout(r, 0)). Chromium also hasscheduler.yield(), which resumes your work with higher priority than other queued tasks; check support before relying on it. - ▸
Web Workers run real parallel JavaScript, each with its own event loop and global scope (
self), talking to the page throughpostMessage. They can't touch the DOM.SharedArrayBufferandAtomicsallow shared memory, but only on pages that are cross-origin isolated (special security headers). - ▸
Some old browser APIs are synchronous and block:
alert,confirm,prompt, synchronousXMLHttpRequest(deprecated on the main thread) andlocalStorage(a small synchronous read from disk). Prefer async APIs in new code.
Remember this
- 1
Synchronous ("sync") code runs in order, one statement at a time, and each statement must finish before the next begins. Everything you've written so far in this course is synchronous:
const total = price * quantity;finishes, then the next line runs. That's easy to reason about: what you read top to bottom is what happens. - 2
JavaScript in a page runs on one thread. A thread is one worker following one list of instructions. The page's main thread does your JavaScript and a lot of the browser's own work: handling clicks and key presses, working out the layout and painting the screen. While your code is running, that worker can do nothing else.
- 3
Blocking means a piece of code keeps the thread busy for a long time. A loop that runs for two seconds blocks the thread for two seconds: buttons don't respond, text can't be selected, animations stop and timers can't fire. Browsers notice when a page is stuck for many seconds and may offer to stop it ("Page unresponsive"). Any task longer than 50 ms is called a long task and makes the page feel slow.
- 4
Many jobs are mostly waiting: asking a server for data, waiting 3 seconds before hiding a message, waiting for the user to click. If JavaScript sat and waited, the page would freeze the whole time. Instead it uses asynchronous ("async") code: you start the job, hand the browser a function to call when it's done (a callback), and your code carries on at once. The waiting happens outside your JavaScript, inside the browser.
- 5
The browser (the host, from Topic 0.1) provides the async tools: timers (
setTimeout, Topic 10.2), network requests (fetch, Phase 11), events (addEventListener, Phase 9) and more. The JavaScript language itself provides promises and async/await (Topics 10.5–10.8), which are tidy ways to handle the results. The rule that decides when each result is handled is the event loop (Topic 10.3). - 6
Async doesn't make work faster, and it doesn't run your JavaScript in parallel. It only stops waiting from blocking the thread. Heavy computing (sorting a million items, processing an image) still blocks, because it really uses the thread. For that, you split the work into small pieces or move it to a Web Worker: a separate thread with no access to the page.
Explain it without notes
What is the difference between synchronous and asynchronous code?
What does it mean that JavaScript in the browser is single-threaded, and why doesn't the page freeze while waiting for a server?
Why does setTimeout(fn, 0) not run fn immediately?
If async doesn't add threads, how do you keep a page responsive during heavy calculation?
Practice
Write code that logs Start, then after 200 ms logs Middle, and logs End straight after Start. Predict the order.
This code is meant to print Loaded: yes but prints Loaded: no. Fix it without changing the 100 ms delay.
let loaded = "no"; setTimeout(() => { loaded = "yes"; }, 100); console.log("Loaded:", loaded);
Trade-offs
- ↔
Synchronous code is simpler to read and debug; asynchronous code keeps the page responsive but spreads one job over several moments in time, which makes order and errors harder to follow.
- ↔
Splitting heavy work into chunks keeps the page responsive but the total job takes a little longer; a Web Worker keeps the main thread completely free but needs message passing and can't touch the page.
Done when you can
I can explain synchronous and asynchronous code with an everyday example.
I know the page's JavaScript runs on one main thread that also handles input and drawing.
I can recognise blocking code and explain what the user sees.
I know that the browser does the waiting and my callback runs later.
I know async doesn't speed up heavy computing, and what to do instead.