Understanding Synchronous vs Asynchronous in JavaScript

sync vs async in js

When learning JavaScript, one of the most confusing — yet essential — concepts is understanding how synchronous and asynchronous code works.
In this blog post, I’ll explain the differences clearly, with code snippets, and simple explanations that anyone can follow.

Code Example: Sync vs Async in Action

console.log("Hi!");

setTimeout(() => {
  console.log("I am inside setTimeout");
}, 0);

Promise.resolve().then(() => {
  console.log("I am inside Promise");
});

console.log("Bye!");

You might guess the output of the above code would be:

Hi!
I am inside setTimeout
I am inside Promise
Bye!

But the actual output is:

Hi!
Bye!
I am inside Promise
I am inside setTimeout

🧠 JavaScript is Single-Threaded

JavaScript runs on a single thread, meaning only one task executes at a time using a structure called the Call Stack. However, JavaScript handles asynchronous operations (like timers or API calls) through:
-Web APIs (provided by the browser)
-Callback Queue (aka Macrotask Queue)
-Microtask Queue (Promises and MutationObservers)
The Event Loop

Synchronous Code Explained

Synchronous code executes line by line, directly on the call stack:

console.log("Hi!");
console.log("Bye!");

These statements go to the call stack immediately and execute in order:

Hi!
Bye!

Asynchronous Code: setTimeout() and Promise

Let’s break it down:

setTimeout
setTimeout(() => {
  console.log("I am inside setTimeout");
}, 0);

-setTimeout() is called and enters the call stack.
-It’s passed to the browser’s Web API, which starts a timer.
-After the timer expires (even with 0ms), it moves to the callback (macrotask) queue.
-The event loop checks if the call stack is empty and then moves it to the call stack for execution.

Promise
Promise.resolve().then(() => {
  console.log("I am inside Promise");
});

-The Promise.resolve().then(…) enters the call stack.
-Then it is added to the microtask queue by JavaScript engine
-After all synchronous code runs, microtasks are given priority and executed before any macrotasks.

So the Promise message appears before setTimeout even though both were set with no delay.

How the Event Loop Works (Simplified)

The Event Loop constantly checks:

  1. Is the call stack empty?
  2. If yes, are there any microtasks in the queue?
    • If yes, move them one by one to the call stack and run them.
  3. After microtasks are done, check the macrotask queue.
    • Move them one by one to the call stack and run them.

Note – It’s not the event loop that places Promises in the microtask queue — the JavaScript engine does it when a Promise resolves.

Final Execution Order Breakdown

  1. console.log("Hi!") → Synchronous → call stack
  2. console.log("Bye!") → Synchronous → call stack
  3. Promise.then() → Microtask queue → runs after sync
  4. setTimeout() → Macrotask queue → runs after microtasks

So the final output is:

Hi!
Bye!
I am inside Promise
I am inside setTimeout

Summary Table for Reference

Code TypeGoes to Call Stack?Offloaded ToQueue TypeRuns When?
console.log()✅ Yes❌ NoneNoneImmediately (during sync execution)
setTimeout()✅ Yes✅ Web APIMacrotask QueueAfter all microtasks complete
Promise.then()✅ Yes✅ JS Engine (async job)Microtask QueueRight after synchronous code ends

Conclusion

JavaScript is single-threaded but handles complex asynchronous operations using Web APIs, task queues, and the event loop. Understanding this flow helps you debug timing issues and build better code.

Key Takeaways:

  • Synchronous code runs first.
  • Promises (microtasks) run before setTimeout (macrotasks).
  • setTimeout(..., 0) still doesn’t run immediately!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top