DEV Community

Cover image for Understanding the Event Loop, Synchronous vs Asynchronous Code (Explained for Beginners)
Ebenezer
Ebenezer

Posted on

Understanding the Event Loop, Synchronous vs Asynchronous Code (Explained for Beginners)

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");
Enter fullscreen mode Exit fullscreen mode

What do you think JavaScript will print?

Many beginners guess:

Start
Timeout
Promise
End
Enter fullscreen mode Exit fullscreen mode

But the real output is:

Start
End
Promise
Timeout
Enter fullscreen mode Exit fullscreen mode

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:

  1. Immediate work
  2. Very important quick tasks
  3. 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");
Enter fullscreen mode Exit fullscreen mode

Output:

Hello
World
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

Step 1 — Synchronous Code Runs First

JavaScript executes:

Start
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

So the output becomes:

Start
End
Enter fullscreen mode Exit fullscreen mode

Step 5 — Run Microtasks

The event loop now checks the microtask queue.

The Promise callback runs next.

Promise
Enter fullscreen mode Exit fullscreen mode

Step 6 — Run Macrotasks

Finally JavaScript checks the macrotask queue.

Now the setTimeout callback runs.

Timeout
Enter fullscreen mode Exit fullscreen mode

Final Output

So the final result becomes:

Start
End
Promise
Timeout
Enter fullscreen mode Exit fullscreen mode

Once you understand the event loop, this result becomes predictable.


The Simple Rule to Remember

JavaScript executes tasks in this order:

  1. Synchronous code
  2. Microtasks (Promises)
  3. 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");
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
trinhcuong-ast profile image
Kai Alder

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 after await actually 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:

setTimeout(() => console.log("1"), 0);
Promise.resolve().then(() => {
  console.log("2");
  setTimeout(() => console.log("3"), 0);
});
Promise.resolve().then(() => console.log("4"));
Enter fullscreen mode Exit fullscreen mode

Nesting microtasks and macrotasks is where it gets really interesting.