JavaScript Interview Puzzle #3
Over the past few days in this JavaScript Interview Puzzle series, we explored some small pieces of code that behave in surprising ways.
Each puzzle looked simple.
But behind the scenes, JavaScript was following a set of rules that many beginners don't notice at first.
Today’s puzzle introduces one of the most important concepts in JavaScript:
• Synchronous code
• Asynchronous code
• Microtasks
• Macrotasks
• The Event Loop
These ideas power almost everything modern JavaScript does.
From loading websites…
to calling APIs…
to updating user interfaces.
Let’s explore them together through a small puzzle.
The Puzzle
Look at the code below carefully.
Before scrolling further, pause and try to predict the output.
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
What do you think JavaScript will print?
Many beginners guess:
Start
Timeout
Promise
End
But the real output is:
Start
End
Promise
Timeout
At first glance, this might feel confusing.
To understand why this happens, we need to understand how JavaScript runs code.
Let’s Pause for a Simple Story
Imagine a small office with one worker.
This worker can only do one task at a time.
People keep giving the worker different tasks.
Some tasks are quick.
Some tasks take time.
Some tasks are extremely important and must be handled as soon as possible.
So the worker organizes the tasks into three categories:
- Immediate work
- Very important quick tasks
- Tasks that can wait
JavaScript organizes work in a very similar way.
Synchronous Code (Immediate Work)
Synchronous code is the simplest type of code.
It runs line by line, exactly in the order it appears.
Example:
console.log("Hello");
console.log("World");
Output:
Hello
World
JavaScript executes the first line.
Then it executes the next line.
Then the next.
Nothing jumps ahead.
Nothing waits.
Everything happens in order.
This is called synchronous execution.
Asynchronous Code (Tasks That Take Time)
Some tasks take time.
For example:
• Calling a server
• Waiting for user input
• Loading data from a database
• Running a timer
If JavaScript waited for each task to finish, websites would feel slow.
So instead, JavaScript allows certain tasks to run asynchronously.
Example:
setTimeout(() => {
console.log("Task finished");
}, 2000);
This means:
"Run this function after 2 seconds."
Instead of waiting, JavaScript continues executing other code.
This makes applications feel fast and responsive.
The Event Loop (The Task Manager)
JavaScript has something called the Event Loop.
Think of it as a manager that constantly asks:
"Is there any work waiting to be done?"
The event loop checks different queues and decides which task should run next.
To do that, JavaScript organizes tasks into two main queues:
• Microtasks
• Macrotasks
Microtasks (High Priority Tasks)
Microtasks are very important small tasks.
They always run before normal tasks.
Promises usually create microtasks.
Example:
Promise.resolve().then(() => {
console.log("Promise finished");
});
Even though this runs asynchronously, JavaScript treats it with higher priority.
That’s why Promise callbacks run before many other asynchronous tasks.
Macrotasks (Normal Tasks)
Macrotasks are normal asynchronous tasks.
Examples include:
• setTimeout
• setInterval
• setImmediate
Example:
setTimeout(() => {
console.log("Timer finished");
}, 0);
Even with a delay of 0, this task still waits in the macrotask queue.
JavaScript will only run it after finishing all microtasks.
Now Let’s Solve the Puzzle
Let’s go back to the code.
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Step 1 — Synchronous Code Runs First
JavaScript executes:
Start
Step 2 — setTimeout is Scheduled
JavaScript schedules the timer.
But it does not run yet.
Instead it goes into the macrotask queue.
Step 3 — Promise is Scheduled
The Promise callback is placed in the microtask queue.
Step 4 — Continue Synchronous Code
JavaScript runs the final synchronous line.
End
So the output becomes:
Start
End
Step 5 — Run Microtasks
The event loop now checks the microtask queue.
The Promise callback runs next.
Promise
Step 6 — Run Macrotasks
Finally JavaScript checks the macrotask queue.
Now the setTimeout callback runs.
Timeout
Final Output
So the final result becomes:
Start
End
Promise
Timeout
Once you understand the event loop, this result becomes predictable.
The Simple Rule to Remember
JavaScript executes tasks in this order:
- Synchronous code
- Microtasks (Promises)
- Macrotasks (Timers, setTimeout)
This rule explains many asynchronous behaviors in JavaScript.
Why This Matters in Real Applications
Understanding the event loop helps developers work with:
• API requests
• UI updates
• React state updates
• Async/await
• Performance optimization
Many bugs in real applications happen because developers misunderstand how asynchronous tasks are scheduled.
Once you understand the event loop, these problems become much easier to solve.
A Small Challenge
Before running the code, try to predict the output.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
Use the rule we learned.
Which one runs first?
Final Thought
JavaScript might seem unpredictable at times.
But most of the time it is simply following clear rules.
Once you understand synchronous execution, asynchronous tasks, microtasks, and macrotasks, the language becomes far easier to reason about.
And puzzles like this are a great way to build that understanding step by step.
Your Turn
Without running the code:
What do you think the challenge example prints?
Write your answer in the comments before testing it.
Tomorrow we will continue this JavaScript Interview Puzzle series with another small piece of code that reveals an interesting behavior of the language.
See you tomorrow.
Top comments (1)
For the challenge: A, D, C, B — same pattern as the main puzzle. Sync first, then microtasks, then macrotasks.
Nice breakdown btw. The office worker analogy is solid for beginners. One thing that tripped me up early on was
async/await— it looks synchronous but the stuff afterawaitactually goes into the microtask queue too. That blew my mind when I first figured it out debugging a race condition.Here's a fun follow-up puzzle if you want to make people's heads spin:
Nesting microtasks and macrotasks is where it gets really interesting.