cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

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.

By the end you can explain
  • 01
    Why blocking freezes the pageConnect long synchronous work to an unresponsive main thread.
  • 02
    What the host does for JavaScriptDescribe timers, network, and file APIs as work the host waits on.
  • 03
    Why 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.

Real-life analogySync is a phone call; async is a text

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.

Working definition

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

INTERACTIVE

Blocking 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.

Blocking vs non-blocking kitchen
Two ways to waitPop out in the code editor (opens in a new tab)JavaScript
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);}
Live counteridle
Tick 0

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.
Try it yourself

Run the synchronous bake, then reset and run the asynchronous bake. Watch whether the counter moves.

The blocking loop is intentionally capped. It demonstrates the freeze without copying the longer main-thread freeze demo from Engines, runtimes & hosts.

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

INTERACTIVE

The 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.

Real-life analogyThe host is a personal assistant

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.

Host APIs do the waiting
Visible codePop out in the code editor (opens in a new tab)JavaScript
console.log("1. JS starts the recipe");setTimeout(() => {  console.log("3. host puts the callback back");}, 1000);console.log("2. JS keeps cooking");
Desk diagramready
JavaScript deskStarts timer, keeps working
Host assistantCounts the 1000 ms outside JS
Callback noteGoes back in the queue for later
Step 1 of 3JS continues

Press Start. JavaScript will start the timer and keep going while the host counts.

This is a conceptual diagram, not an engine inspector. The timing is real and cleaned up when the component unmounts.

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 THROUGH

A 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.”

A, C, then B
Step 0 of 4Ready
Your turn: follow the blue line

Predict the console order. The timer delay is 0, but the callback still runs after the current script finishes.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
setTimeout(() => {  console.log("B: timer callback");}, 0);console.log("C: end");
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Zero is not now

setTimeout(callback, 0) still crosses the host boundary. The callback becomes a later task. That is why A prints, then C, then B.

Sync or async?
  • console.log("ready")
  • setTimeout(() => console.log("ding"), 0)
  • a fetch response handler
  • the callback passed to array.map
  • button.addEventListener("click", handler)
  • JSON.parse("{\"ok\":true}")
Try it yourself
0 of 6 correct

Sort each callback or operation by whether it runs during the current turn or later after a host event.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Run to completion

STEP THROUGH

Run 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 cannot interrupt
Step 0 of 7Ready
Your turn: follow the blue line

The timer is ready quickly, but run-to-completion means the loop in your hand finishes before the note is picked up.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  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");
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Synchronous work vs asynchronous waiting
QuestionSynchronousAsynchronous
Who waits?JavaScript waits on the main threadThe host waits outside JavaScript
Can the page respond meanwhile?No, not while JS is busyUsually yes, because JS is free between callbacks
When does the callback run?There may be no callback; work happens nowLater, after the current task completes
Examplesconsole.log, JSON.parse, array.mapsetTimeout, 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.

A practical rule of thumb

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 EXERCISES
Exercise 1 · Warm-upPredict the log order

Predict the console output order without running it, then check your answer.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log("A: start");
setTimeout(() => {
  console.log("B: timer callback");
}, 0);
console.log("C: end");

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeConvert a blocking wait

    Replace the busy loop with a host API so the page can remain responsive. Which API should you reach for?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function waitOneSecond() {
      const started = Date.now();
      while (Date.now() - started < 1000) {}
      console.log("done");
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWhy do alert and prompt feel frozen?

      Explain whether alert and prompt block the main thread.

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeSpot callbacks that run later

        Name two callbacks from this lesson that run later instead of during the current line of code.

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeIdentify run-to-completion

          Can the timer callback interrupt the loop? Answer yes or no, then explain why.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          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");

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Read each explanation, especially when a wrong answer almost sounded right.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does synchronous code mean?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("A");
              setTimeout(() => console.log("B"), 0);
              console.log("C");

              Choose an answer to see the explanation.

            3. Question 3 of 7Who does the waiting for setTimeout?

              Choose an answer to see the explanation.

            4. Question 4 of 7Which callback usually runs during the current synchronous turn?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does run to completion mean?

              Choose an answer to see the explanation.

            6. Question 6 of 7Why can JSON.parse freeze a page on huge input?

              Choose an answer to see the explanation.

            7. 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 map run 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.

            CompleteFrontend Clear concepts. Working examples.