A short but load-bearing lab: JavaScript can only ever do one thing at a time. Every lab
since Lab 05 has quietly relied on this — now it becomes explicit.
What this lab produces: no files, no commit — a pure concept lab. You'll run
one short snippet in a console or with node.
Concept type: Common / Universal Concept
▶
EXECUTION10 of 10
⏱
ASYNCHRONY & CONCURRENCY9 of 10
This constraint on execution is exactly what will later shape JavaScript's entire answer to
"how do we deal with waiting on something."
Mental Models
Mental Model 1 of 5One Stack, One Active Thing, Always
There's no second call stack running somewhere else in parallel. Every lab so far has really been describing the only execution path JavaScript ever has.
Mental Model 2 of 5Synchronous = Fully Finish, Then Advance
Each line of code runs to completion before the engine even considers the next one — there's no partial overlap, no "starting the next line a little early."
Mental Model 3 of 5A Busy Loop Blocks Everything
Since there's only one thread, a synchronous block that takes a long time to run doesn't just delay other JavaScript — it delays timers, click handling, and page rendering too, all of which share that same single thread.
Mental Model 4 of 5An Analogy: One Cashier, One Register
A single cashier can only ever help one customer at a time, start to finish, no matter how long the line behind them gets. Every other customer simply waits their turn — nobody gets served "a little bit" in parallel.
Mental Model 5 of 5Misconception: "Async" ≠ a Second Thread
Seeing the word async in code can suggest something is now running in parallel. It isn't — JavaScript still has exactly one thread and one call stack. What "async" code actually does with that single thread is the subject of several labs much later in this curriculum.
Learning Objectives
Objective 1 of 3 — I can state, precisely, what "single-threaded" and "synchronous" each mean, and explain that they're two related but distinct properties.
Objective 2 of 3 — I can predict what happens to a scheduled timer callback while a long synchronous block is still running.
Objective 3 of 3 — I can explain why the word async in JavaScript does not mean "runs on a separate thread."
Pre-Lab Quiz
Answer from intuition first.
Question 1 of 5setTimeout(fn, 0) schedules fn to run after 0 milliseconds. Does that mean fn runs immediately, interrupting whatever code is currently running?Open question
Question 2 of 5A synchronous loop takes 5 seconds to finish. During those 5 seconds, can a user's click on the page be handled?Open question
Question 3 of 5Does marking a function async cause its code to run on a separate thread from the rest of the program?Open question
Question 4 of 5Can two ordinary (non-async) JavaScript functions ever be mid-execution at the exact same instant?Open question
Question 5 of 5If a CPU-heavy synchronous loop runs inside a webpage, will the page visually freeze (no scrolling, no repainting) while it runs?Open question
The Concept
Two related but distinct words: single-threaded and synchronous
Single-threaded means there is exactly one thread — one literal sequence
of instructions the CPU is following for your program — with no second thread running
alongside it to pick up other work. Synchronous means each instruction
fully completes before the next one begins; there's no partial overlap between one line and
the next. JavaScript is both: because it's single-threaded, its normal execution has to be
synchronous — with only one thread available, there's nothing else that could run
concurrently with the current line anyway.
Every lab so far has already been describing this constraint
Lab 05's call stack only ever has one active context because there's only one thread to run
it on. Lab 09's scope chain search happens as one uninterrupted operation, with no other
code able to run partway through it. None of this was ever stated as a separate rule before
now — it was the unspoken assumption underneath everything already covered.
What actually happens with something scheduled for "later"
If JavaScript can only run one thing at a time, what happens to something like a timer
callback? It doesn't run early, and it doesn't interrupt whatever's currently executing —
it waits until the single thread is completely free, meaning every currently-running
synchronous line of code has fully finished, all the way back down to an empty call stack.
Only then does the engine even consider starting something new. (The full mechanism behind
exactly how that "something new" gets picked is the event loop, covered starting
at Lab 41 — this lab is only about the constraint that makes an event loop necessary in the
first place.)
Lab
Primary objective: confirm directly that a scheduled callback can never interrupt currently-running synchronous code, no matter how short its delay.
You'll need: a browser console or node — no project, no files.
Lab 1 of 1 — Try to interrupt the single thread
Task
Schedule a timer with a 0ms delay, then keep the thread busy, and see whether the timer can cut in line.
Instructions
Step 1 of 4
// Paste this into a scratch file or console. Read it — do not run it yet.
This step is complete when: you've predicted the order of all three logs without running it.
Step 2 of 4
Predict the exact order the three lines print in, then reveal.
Output:
start
end
timer fired
What you wrote: a 0ms timer sitting between two synchronous logs.
What the engine effectively does: "0ms" only ever means "as soon
as the thread is free," not "immediately." The synchronous code --
both console.log calls -- fully finishes first, emptying the call
stack. Only then does the timer callback get a turn, regardless of
how short its requested delay was.
This step is complete when: you've run the snippet yourself and it matches the revealed order.
Step 3 of 4
// Edit the file yourself. Don't paste finished code — type it from these comments.
// Between the setTimeout call and the final console.log("end"),
// add a synchronous loop that takes a noticeable moment to finish:
// declare "var total = 0;", then a for loop from i = 0 to
// less than 500000000, adding i to total each time.
// Change the final console.log to also print total, e.g.
// console.log("end", total);
This step is complete when: the busy loop is added between the timer and the final log, and the final log includes total.
Step 4 of 4
Predict: does the timer callback manage to sneak in and run partway through the loop, since the loop now takes a noticeable amount of time? Run it and compare, then answer this comprehension check: if this loop ran inside an actual webpage instead of a console, what would the user visually experience while it's running, and why?
Comprehension Checklist
I can explain, without hand-waving, why "0ms" doesn't mean "immediately" for a timer.
I can predict that a long synchronous loop delays timers, clicks, and page rendering equally, since all three share the same thread.
I know the word async does not, by itself, create a second thread.
I can connect this lab's constraint to why every previous lab's call-stack model only ever has one active context.
Post-Lab Quiz
These are different questions from the pre-lab quiz — they test what the experiment should
have taught you, not the same questions again.
Question 1 of 5In the busy-loop version of the experiment, did the timer callback manage to run at any point during the loop?Open question
Question 2 of 5If this loop ran inside a real webpage, what would the user experience while it's running?Open question
Question 3 of 5Suppose two setTimeout(fn, 0) calls are scheduled back to back, both before any other synchronous code runs. Can their callbacks execute at the same time as each other?Open question
Question 4 of 5Does increasing a setTimeout's delay from 0ms to, say, 5000ms change whether it can interrupt currently-running synchronous code?Open question
Question 5 of 5Given everything in this lab, what's actually wrong with the sentence "JavaScript runs the setTimeout callback in the background while the loop runs"?Open question
Reflection Questions
Write your own answer first — the blurred text is one possible answer, not the only correct one.
Reflection 1 of 3. Before this lab, if someone had asked you "does JavaScript run things in parallel," what would you have said, and why?
Words like "async," "background," and "concurrently" are common enough in everyday
descriptions of JavaScript that it's easy to assume some form of real parallelism is
happening. This lab's point is that none of that vocabulary changes the underlying fact:
exactly one thread, exactly one thing running at any instant.
Reflection 2 of 3. Have you ever seen a webpage freeze up completely during some heavy operation? Reframe that experience using this lab's model — what was actually happening on the single thread at that moment?
A frozen page during heavy JavaScript work is this lab's busy-loop experiment happening
for real — some synchronous block of code occupied the single thread long enough that
rendering, scrolling, and click handling all had to wait in line behind it, with no way
to cut in.
Reflection 3 of 3. This lab said the full mechanism for "how something scheduled for later actually gets picked to run" is covered starting at Lab 41. Based only on what you know now, what's one specific question you'd want that later lab to answer?
A natural question: if two different callbacks both become eligible to run at the same
moment — say, a timer and a network response arriving at the same time — which one
actually gets to go first, and is that decision predictable or arbitrary? (It's
predictable, and Lab 41 onward covers exactly how.)
What Breaks If This Understanding Is Wrong?
If you believe JavaScript can genuinely run two pieces of your own code at the same instant,
the entire event-loop model later in this curriculum will look contradictory instead of
logical — every explanation of task queues and microtasks assumes, as a starting fact, that
only one thing can ever be actively running.
What JavaScript Concept Was Learned Today?
JavaScript is single-threaded and synchronous by default — exactly one thing runs at a
time, and a scheduled callback can never interrupt code that's already running, no matter
how short its requested delay.
Relates to: ▶ Execution (10 of 10) + ⏱ Asynchrony & Concurrency
(9 of 10) — the execution constraint that makes an entire async/event-loop model necessary
in the first place.
Next
That completes the Foundation section — execution contexts, the call stack, hoisting,
variable declarations, the global object, the scope chain, and now the single-threaded
constraint tying them all together. Lab 11 moves into Functions as a First-Class Concept,
starting from an idea this section has already leaned on quietly: that a function is
itself a value.