Skip to content
AtomicReps
The Event Loop You Stopped Drawing

Nothing interrupts a running stack

Lesson 1 of 28

Nothing interrupts a running stack

A lesson from The Event Loop You Stopped Drawing. Play it above, or read it through below.

  • The call stack finishes what it started

    The common read: a timer whose delay has already elapsed interrupts the running function to fire on time.

    The call stack is where JavaScript keeps track of which function is running and who called it. One function runs at a time, and while it runs, it owns the stack completely. Nothing preempts it: not a timer, not a network response, not a click. This is run-to-completion, and it holds for every function, all the time, with no exception carved out for urgency.

    "Ready" and "running" are two different states, and the gap between them is the whole lesson. setTimeout's callback became ready the moment its delay elapsed, but ready work does not join the stack. It parks in the task queue instead, and it waits there until the running function returns and the stack goes empty. Only then does the loop hand the stack to the next thing in line.

    This is why the probe's timer logs last. before and after both belong to the function that's running; the timer's callback was never allowed to interrupt it, no matter how overdue it got.

  • One task queue, one task per turn

    The common read: a click that happens while a function is running is either lost entirely or runs its handler the instant the click occurs.

    Every piece of work that isn't already running enters through the same door: the task queue. A timer's callback, a click handler, a scheduled response, all of it lines up in the same place, waiting for the same condition, an empty call stack.

    The loop's job is simple and strict: check whether the stack is empty, and if it is, take exactly one task off the front of the queue and run it to completion. Not two, not a batch, one. That task now owns the stack the same way the previous one did, and it keeps the stack until it returns, same rule as S2.

    This is why the click in S4 neither vanishes nor cuts in. It joins the queue behind whatever the browser already has waiting, and it gets its turn exactly once the running function is done and the loop comes back around.

  • What run-to-completion buys you, and what it costs

    Run-to-completion is the reason a function never has to guard against being interrupted halfway through. No other code can see a half-updated object or a DOM half-way through a rewrite, because no other code gets to run until the current function is done touching it. That guarantee is why so much JavaScript code has no locks, no mutexes, nothing.

    The cost is the flip side of the same rule. A function that runs long keeps that guarantee by holding the stack, and holding the stack means nothing else runs either, not the next task, not a click handler, nothing. A 500ms loop is 500ms where the page can't respond to anything. (If a tab has ever gone grey and stopped responding while you waited, that's this rule enforcing itself, not a bug in the browser.)

    This isn't the last word on what parks in a queue and waits its turn. A later lesson in this course looks at what happens when ready work keeps arriving faster than a running function ever lets go of the stack, and why a page can freeze itself on purpose. That's a different failure mode from a single long loop, and it needs a queue this lesson hasn't introduced yet.

  • One question sorts every case in this lesson

    Every scenario in this lesson reduces to one question: is the call stack empty right now? While the answer is no, nothing else runs, not a timer past its delay, not a click, not any other queued task. The moment the answer becomes yes, the loop takes exactly one thing off the task queue and the same rule applies to it: it now owns the stack until it, too, returns.

    Nothing in this lesson depends on what kind of task is waiting. A timer callback, a click handler, and every other task source share one queue and one rule. The lesson after this one adds a second queue with a different draining rule; nothing here needs to change to make room for it, because the call stack's half of the story is already complete.