Why Promises run before Timeouts: A visual guide to the Event Loop
Timothy stood at the drafting table, watching Margaret erase a large section of the chalkboard.
"I am worried about the architecture, Margaret," he said.
"Oh?" she replied, dusting chalk from her hands.
"We established that JavaScript is Single-Threaded," Timothy said. "It has one brain. It can only do one thing at a time. If I write a function that takes five seconds to run, the entire browser freezes. Nothing else can happen until that function finishes."
"That is true," Margaret agreed. "If you block the Call Stack, the world stops."
"Then how does anything work?" Timothy asked, frustrated. "We fetch data, we set timers, we wait for user clicks... all of these take time. Why doesn't the interface freeze every time we wait for a server?"
Margaret picked up a fresh piece of chalk. "Because, Timothy, while the Language is single-threaded, the Browser is not."
The Call Stack (The One Track Mind)
She drew a tall, narrow box on the left side of the board and labeled it Call Stack.
"This is where your code runs," she explained. "It follows a simple rule: One Thing at a Time. I put a function on the Stack, I run it, and I take it off. I cannot do anything else while I am working on the top item."
"Right," Timothy nodded. "That's the bottleneck."
Offloading (Web APIs)
Margaret drew a second box to the right, separate from the Stack. She labeled it Web APIs.
"This is the trick," she said. "When you run a command like setTimeout or fetch, the Stack does not do the waiting."
She wrote a snippet of code:
console.log("Start");
setTimeout(function() {
console.log("Time is up!");
}, 5000);
console.log("End");
"Watch closely," Margaret instructed.
- "I put
console.log('Start')on the Stack. It runs. I pop it off." - "I see
setTimeout. The Stack says, 'I cannot pause for 5 seconds.' So, it hands the timer over to the Web APIs box." - "The Web API starts the timer outside of the Stack."
- "The Stack immediately moves to the next line:
console.log('End')."
"So the Stack never stopped?" Timothy asked.
"Never," Margaret said. "It handed off the heavy lifting and kept working."
The Queue (The Waiting Area)
"But what happens when the 5 seconds are up?" Timothy asked. "Does the Web API force the code back onto the Stack?"
"Absolutely not," Margaret said firmly. "That would interrupt the code currently running. That would be chaos."
She drew a horizontal tray at the bottom of the board. She labeled it Callback Queue.
"When the Web API finishes its timer, it drops the callback function into this Queue. It sits there and waits."
The Event Loop (The Gatekeeper)
Margaret drew a circle connecting the Queue to the Stack.
"This is the Event Loop," she said. "It has one job. It constantly checks the traffic."
"The rule seems simple," Timothy said. "When the Stack is empty, run the Queue."
"Almost," Margaret corrected. She drew a smaller, gold-plated tray sitting on top of the standard Queue. "There is one complication. There is a VIP Line."
"A VIP line?"
"It is called the Microtask Queue," she explained. "Promises go here. The standard Queue is for Macrotasks (like setTimeout). The Event Loop is a snob, Timothy. It always clears the VIP line before it even looks at the standard Queue."
The Race (Promise vs. Timeout)
Timothy grabbed the chalk to test this hierarchy. "So if a Timer and a Promise finish at the exact same moment, the Promise wins?"
"Test it," Margaret said.
Timothy wrote a race condition:
console.log("Start");
setTimeout(() => {
console.log("Timeout (Standard)");
}, 0);
Promise.resolve().then(() => {
console.log("Promise (VIP)");
});
console.log("End");
"Walk through it," Margaret instructed.
- "Start" runs immediately.
-
setTimeoutgoes to Web APIs, finishes instantly (0ms), and drops "Timeout" in the Standard Queue. -
Promiseresolves instantly and drops "Promise" in the VIP Queue. - "End" runs immediately.
"Now the Stack is empty," Timothy said. "Both queues have items waiting. Who goes first?"
"The VIPs," Margaret said.
The output appeared on the console:
// Output:
// Start
// End
// Promise (VIP) <-- Cut in line!
// Timeout (Standard)
Timothy stared at the board. "The Promise cut in line."
"Always," Margaret replied. "The Event Loop will exhaust every single item in the VIP queue before it lets a single timer run."
The Conclusion
Timothy looked at the diagram. The architecture was efficient, but strict.
"The Stack does the work," he summarized. "The Web APIs handle the delays. And the Event Loop manages the traffic."
"Precisely," Margaret said. "It prioritizes urgent logic (Promises) over general delays (Timeouts). As long as we respect the order, the library never freezes."
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
Top comments (1)
This is one of the clearest explanations of the event loop I’ve read.
The story format makes the call stack, Web APIs, and queues feel intuitive instead of abstract.
The “VIP line” analogy for microtasks vs timeouts really helps it click.
Great write-up — this would help a lot of people who struggle with promises and async behavior.