How setTimeout Works Behind the Scenes in JavaScript
setTimeout does not guarantee exact timing. Here is how it schedules callbacks and why it is not trustworthy.
How setTimeout Works Behind the Scenes in JavaScript
setTimeout is one of the most used Web APIs in JavaScript. But it does not work the way most people think. Understanding the mechanism prevents bugs.
What setTimeout Does
setTimeout(callback, delay, ...args); `` 1. The engine calls the **Web API** (provided by the browser or Node.js). 2. The Web API starts a timer for `delay` milliseconds. 3. The engine continues running synchronous code (it does not wait). 4. When the timer fires, the callback is pushed to the **macrotask queue**. 5. The **event loop** moves the callback to the call stack **only when the stack is empty**. ### The Key Insight `setTimeout` does **not** guarantee that the callback will run exactly after `delay`. It guarantees a **minimum** delay. The actual delay depends on: - **The call stack**: if synchronous code is still running, the callback waits. - **The microtask queue**: the event loop drains all microtasks before any macrotask. If microtasks keep coming, the callback waits. - **Timer throttling**: browsers throttle timers in background tabs (often to 1 second minimum). - **Nested timers**: browsers may throttle deeply nested `setTimeout` calls (minimum 4ms after 5 nested levels). ### Example: `setTimeout(cb, 0)` Does Not Run Immediately ```js console.log("1"); setTimeout(() => console.log("2"), 0); console.log("3"); `` **Output**: `1, 3, 2`. The callback goes to the queue. The event loop runs it after the synchronous code finishes. ### Example: Microtasks Delay the Timer ```js setTimeout(() => console.log("timer"), 0); Promise.resolve().then(() => console.log("promise")); console.log("sync"); `` **Output**: `sync, promise, timer`. The promise callback (microtask) runs before the timer callback (macrotask), even though both have 0 delay. ### Example: Blocking the Stack ```js setTimeout(() => console.log("timer"), 0); function block() { const start = Date.now(); while (Date.now() - start < 5000) {} // 5 seconds } block(); `` **Output**: `timer` (after 5 seconds). The callback was in the queue, but the stack was blocked. It ran only after `block` returned. ### The Takeaway `setTimeout` hands the timer to a Web API, continues running synchronous code, and queues the callback when the timer fires. The event loop runs the callback only when the stack is empty and microtasks are done. `setTimeout` does not guarantee exact timing; it guarantees a minimum delay. Blocking the stack delays timers.
It hands the timer to a Web API, continues running synchronous code, and when the timer fires, pushes the callback to the macrotask queue. The event loop runs the callback only when the call stack is empty and microtasks are done.
No. The callback goes to the macrotask queue. The event loop runs it only after the current call stack is empty and all microtasks are done. So it runs after synchronous code.
Because promise callbacks go to the microtask queue, which the event loop drains completely before touching the macrotask queue where setTimeout callbacks live. Microtasks always run before macrotasks.
No. It guarantees a minimum delay, not an exact delay. The actual delay depends on the call stack, the microtask queue, timer throttling in background tabs, and nested timer throttling.
Yes. If synchronous code is still running, the callback stays in the queue. The event loop cannot push it to the stack until the stack is empty. A 5-second blocking function delays all queued timers by 5 seconds.
Ready to master React completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master React
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

