Trust Issues with setTimeout()

Key Concept

setTimeout() does not guarantee the execution of the callback exactly after the given time (e.g., 5000ms). It guarantees that the callback will be executed at least after the given time, but only when the call stack is empty.


Let's Observe the Following Code:

console.log("Start");

setTimeout(function cb() {
  console.log("Callback");
}, 5000);

console.log("End");
// Suppose: Millions of lines of code follow...

๐Ÿงพ Output:

Start
End
// After ~10s (not 5s, even though timer is 5s)
Callback

Why Does This Happen?

๐Ÿ” Step-by-step Execution:

  1. GEC (Global Execution Context) is created and pushed into the Call Stack.

  2. "Start" is printed.

  3. setTimeout(cb, 5000) is encountered:

    • The callback cb() is sent to the Web APIs environment.

    • A timer of 5 seconds starts running independently.

    • JavaScript continues immediately without waiting.

  4. "End" is printed.

  5. Suppose the remaining code after "End" takes 10 seconds to executeโ€”blocking the main thread.

  6. Meanwhile, the timer expires in 5 seconds, and the callback is pushed to the Callback Queue.

  7. However, since the Call Stack is busy, the Event Loop keeps waiting.

  8. Only after the Call Stack is empty (after 10s), the Event Loop pushes cb() into the Call Stack.

  9. Then the cb() is executed, printing "Callback".


Concurrency Model of JavaScript

JavaScript uses a Concurrency Model based on:

  • Call Stack

  • Web APIs

  • Callback Queue

  • Event Loop

๐Ÿ“š Learn more on MDN: JavaScript Event Loop


๐Ÿ”ฅ Real-World Example (Blocking the Main Thread)

console.log("Start");

setTimeout(() => {
  console.log("Callback after 2s");
}, 2000);

// Simulate heavy CPU task
let startTime = Date.now();
while (Date.now() - startTime < 5000) {
  // Blocking main thread for 5s
}

console.log("End");

๐Ÿงพ Output:

Start
End
Callback after 2s

Even though the timer is 2s, it prints after ~5s, because the main thread is blocked.


โš ๏ธ The First Rule of JavaScript:

Never block the main thread.
JavaScript is a single-threaded language. Blocking the Call Stack means blocking everything โ€” rendering, user interaction, animations, and even setTimeout() callbacks.


What if setTimeout(..., 0)?

console.log("Start");

setTimeout(() => {
  console.log("Callback");
}, 0);

console.log("End");

๐Ÿงพ Output:

Start
End
Callback

Why?

Even though the timer is 0ms:

  • The callback still goes through the Web APIs โ†’ Callback Queue โ†’ Event Loop.

  • It waits until the Call Stack is empty.

  • Useful to defer lower-priority tasks.


๐Ÿงฐ Practical Use Case: Deferring Execution

function importantTask() {
  console.log("๐Ÿ”ฅ Important Task");
}

function lessImportantTask() {
  console.log("๐Ÿ•“ Less Important Task");
}

// Let important task run first
importantTask();
setTimeout(lessImportantTask, 0);

Even though the timeout is 0, lessImportantTask() waits until the stack is empty.


Summary

ConceptExplanation
setTimeout(fn, delay)Executes fn at least after delay ms
JS is single-threadedOnly one Call Stack
Callback delay reasonCallback waits in queue until Call Stack is empty
delay = 0Still not executed immediatelyโ€”goes through Web APIs and queue
Event LoopContinuously checks if Call Stack is empty, then pulls from queue
0
Subscribe to my newsletter

Read articles from UR Prakash Gupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

UR Prakash Gupta
UR Prakash Gupta

My name is ๐”๐‘ ๐๐ซ๐š๐ค๐š๐ฌ๐ก ๐†๐ฎ๐ฉ๐ญ๐š and I talk about ๐—ง๐—ฒ๐—ฐ๐—ต-๐Š๐ง๐จ๐ฐ๐ฅ๐ž๐๐ ๐ž, ๐—ช๐—ฒ๐—ฏ๐——๐—ฒ๐˜ƒ, ๐——๐—ฒ๐˜ƒ๐—ข๐—ฝ๐˜€ and ๐—Ÿ๐—ถ๐—ณ๐—ฒ๐˜€๐˜๐˜†๐—น๐—ฒ.