Time Waits for No One - Until You Freeze It
A deep dive into the quirks of setTimeout and the secrets to stopping JavaScript time in its tracks.

Fullstack developer with the occasional dabble in DevOps
Search for a command to run...
A deep dive into the quirks of setTimeout and the secrets to stopping JavaScript time in its tracks.

Fullstack developer with the occasional dabble in DevOps
No comments yet. Be the first to comment.
Why the search for an extraordinary life may be the greatest deception of all

The Blink Test, why our screens and speakers are dead technology walking, and the realization that our own eyes are obsolete hardware

The answer is forcing us to decide what humanity becomes next

More than a decade later, we revisit and re-rank the legends who have defined the big and small screen.

Demystifying Apache Kafka: An exploratory journey for newcomers

In JavaScript, setTimeout feels like an unbreakable contract with the future. But what if I told you that you could pause that future? That you can stop the relentless march of the event loop, giving yourself all the time in the world?
This isn't a theoretical exercise; it's a fundamental consequence of how browsers handle JavaScript execution. But before we learn how to stop time completely, it's important to understand that time in JavaScript is already a surprisingly flexible concept. The timers we use are governed by a fascinating set of rules that bend, stretch, and warp our expectations.
The delay you provide to a timer is more of a suggestion than a command. Several factors determine its actual execution time, revealing that our control over the timeline is limited.
The event loop manages at least two different queues: the Task Queue for things like setTimeout callbacks, and the Microtask Queue for promise callbacks (.then()). After any script runs, the event loop will completely empty the Microtask Queue before processing a single item from the Task Queue.
This gives promises a higher execution priority and explains why this code:
setTimeout(() => console.log('Timeout (Task)'), 0);
Promise.resolve().then(() => console.log('Promise (Microtask)'));
// Output:
// Promise (Microtask)
// Timeout (Task)
...logs the promise first, even though both are scheduled for "as soon as possible."
Browsers actively manipulate your timers to improve performance and save battery life.
The 4ms Clamp: As defined by the HTML spec, after five levels of nested setTimeout calls, the browser will clamp the timeout to a minimum of 4 milliseconds, preventing high-frequency recursive loops from hogging the CPU.1
Inactive Tab Throttling: To conserve resources, browsers will aggressively throttle timers in background tabs, often limiting them to running no more than once per second. Your application's time literally slows down when it's not in focus.
The maximum delay for setTimeout is governed by a 32-bit signed integer, which has a max value of 2,147,483,647 milliseconds. If you set a timer for longer than roughly 24.8 days, the value overflows into a negative number, and the timer fires almost instantly. Most engines treat a negative delay as 0
So, we've seen that browsers can bend and warp time. But what about stopping it completely? This is where we move from bending the rules to breaking them.

Imagine a time-boxed online quiz. Here are two ways a user can grant themselves infinite time.
alert, confirm)These aren't ordinary functions. They are instructions to the browser's UI to create a modal window. A modal demands user attention, and to enforce this, the browser freezes the rendering and scripting thread of that tab. While the alert is on-screen, the event loop is completely blocked, unable to process the submitQuiz() callback waiting in the queue.
An even more powerful tool is the debugger. By opening the developer tools and placing a breakpoint or clicking the "pause script execution" button, a user pauses the JavaScript engine itself. The entire execution context is frozen in place, achieving the same time-stopping effect as alert().
The core reason this works is that JavaScript's event loop is non-preemptive. It can only run a new task when the previous one has completed. A blocking alert or a debugger pause never allows the current task to "complete," thus starving the event loop.
So, is the event loop literally frozen when you call alert()? It’s a great question with a nuanced answer. While the observable effect is a complete halt, the component that's truly locked up is the main execution thread.
Here’s the step-by-step of what happens:
The Main Thread is Blocked: When a synchronous function like alert() is called, it's pushed onto the call stack. It will not finish its execution (and be popped off the stack) until the user provides input (e.g., clicks "OK"). The main thread is now completely occupied, stuck inside this single task.
The Event Loop is Starved: The event loop has one primary job: check if the call stack is empty. If it is, move the next task from the task queue onto the stack to be executed. Since the call stack is perpetually occupied by alert(), the event loop's condition is never met. It's effectively stuck, unable to perform its function.
Think of the main thread as a worker on an assembly line and the event loop as the manager who places new items on the belt. If the worker gets stuck on a single, difficult item (alert()), they can't move on. The manager (event loop) sees the worker is busy and simply waits, unable to place any new items on the belt.
So, while the main thread is the prisoner, the entire asynchronous system is brought to a standstill. For this reason, "freezing the event loop" is a functionally accurate and effective way to describe the outcome.
Did you know you can use clearTimeout() to cancel a setInterval() and vice-versa?
const myInterval = setInterval(() => console.log('tick'), 1000);
// This works perfectly!
clearTimeout(myInterval);
According to the HTML spec, setTimeout and setInterval share the same pool of timer IDs, making their clearing functions interchangeable.
The ability to pause, delay, and manipulate the client-side clock demonstrates a core security principle: never trust the client. While you can freeze your browser's perception of time, you can't stop the clock on the server. For anything that requires integrity: quizzes, auctions, session expirations, the server must remain the single, un-freezable source of truth.