Leaderboard
Javascript May 18, 2026
Javascript Event Loop Microtask Macrotask Order

JavaScript Event Loop — What is the Output?

This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Event Loop Microtask Macrotask Order and improve your technical interview readiness.

console.log(1)
setTimeout(() => console.log(2), 0)
Promise.resolve().then(() => console.log(3))
console.log(4)
A 1, 2, 3, 4
B 1, 4, 2, 3
C 1, 4, 3, 2
D 1, 3, 4, 2

Detailed Explanation

Why This Question Matters

If you've ever spent three hours debugging a race condition or wondered why your "instant" setTimeout didn't actually run instantly, you've run into the JavaScript Event Loop.

This specific code snippet is a classic interview filter. It looks trivial, but it separates people who just "know" JavaScript syntax from those who actually understand how the engine executes code. The confusion usually stems from a misunderstanding of how the browser (or Node.js) prioritizes different types of asynchronous tasks.

Most developers assume that setTimeout(..., 0) means "run this immediately." In reality, JavaScript has a very strict hierarchy of what gets executed first, and the "0ms" delay is a bit of a lie.

Understanding the Code

Let's look at the snippet again:

console.log(1)
setTimeout(() => console.log(2), 0)
Promise.resolve().then(() => console.log(3))
console.log(4)

To figure this out, you have to stop thinking about the code as a linear list and start thinking about it as three different "buckets": the Call Stack, the Microtask Queue, and the Macrotask Queue (often just called the Task Queue).

1. The Call Stack (Synchronous): This is where the engine handles the immediate work. console.log(1) and console.log(4) are synchronous. They hit the stack and execute immediately.
2. The Microtask Queue: This is a high-priority queue. Promises (.then, .catch, .finally) and queueMicrotask go here. The engine will empty this entire queue before it even looks at the macrotask queue.
3. The Macrotask Queue: This is for heavier lifting. setTimeout, setInterval, and I/O operations live here. Even if the timer is set to 0, it still has to wait its turn in this queue.

The execution flow works like this:
- console.log(1) runs. (Output: 1)
- setTimeout is called. The browser takes the callback and tosses it into the Macrotask Queue.
- Promise.resolve().then(...) is called. The callback is tossed into the Microtask Queue.
- console.log(4) runs. (Output: 4)

Now the Call Stack is empty. The engine looks at the Microtask Queue first. It sees the Promise callback and runs it. (Output: 3). Finally, it checks the Macrotask Queue and finds the setTimeout callback. (Output: 2).

Finding the Correct Answer

The correct answer is 1, 4, 3, 2.

If you guessed 1, 2, 3, 4, you assumed the code runs in order. It doesn't.
If you guessed 1, 4, 2, 3, you thought setTimeout had priority over Promises. It doesn't.

The logic is simple: Synchronous code > Microtasks > Macrotasks.

The synchronous logs happen first. Then, the engine checks if there are any "urgent" microtasks (Promises) to clear out. Only after the microtask queue is completely empty does the event loop pick up the next macrotask from the timer queue.

Common Mistakes Developers Make

The biggest trap is the setTimeout(..., 0).

Newer developers think 0 means "now." It actually means "as soon as the stack is clear and all pending microtasks are finished." In a heavy application, a setTimeout with 0ms could actually take dozens of milliseconds to execute if the main thread is bogged down by a massive chain of Promise resolutions.

Another common mistake is forgetting that async/await is just syntactic sugar for Promises. If you see an await keyword, everything *after* that await is effectively wrapped in a .then() block and moved to the Microtask Queue.

Real-World Usage

You aren't going to write console.log sequences like this in production, but you deal with the Event Loop every day.

For example, if you're building a UI component and you need to wait for the DOM to update before measuring an element's size, you might use setTimeout. Why? Because you need the current execution stack to clear and the browser to perform a repaint before you get the correct dimensions.

Similarly, if you're writing a high-performance data processing loop, you might use setImmediate (in Node.js) or setTimeout to "break up" a long-running synchronous task. This prevents the UI from freezing because it gives the browser a chance to breathe (and process other tasks) between chunks of work.

Key Takeaways

- Synchronous code always wins. It runs to completion before anything else starts.
- Promises are "fast-track" async. They go into the Microtask Queue and execute before any timers.
- setTimeout is a "slow-track" async. Even at 0ms, it's a macrotask and waits until the end of the line.
- The Event Loop sequence: Call Stack $\rightarrow$ Microtasks $\rightarrow$ Macrotasks.

Why this matters

Understanding Event Loop Microtask Macrotask Order is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.

📝
Reviewed by CodeShot Editorial
Every challenge is code-reviewed by senior developers to ensure accuracy and real-world relevance. Learn more.

Ready for your shot?

Join thousands of developers solving one logic puzzle every morning.

Solve Today's Challenge →