‘Synchronous’ and ‘Non-Blocking’ Walk Into a Bar… And JavaScript Happens

Wait… JavaScript Does What Now?

Recently, while exploring how Node.js works under the hood, I stumbled upon a fascinating contradiction in JavaScript—and I couldn’t help but share it.

Mind Blown | JAVASCRIPT IS SYNCHRONOUS AND NON-BLOCKING | image tagged in mind blown | made w/ Imgflip meme maker

Wait, what?

If you stare at that statement long enough, it feels like a paradox. Synchronous code should mean line-by-line execution—each step waits for the previous one to finish. But non-blocking means... it doesn’t wait?

So how does JavaScript pull this off?


Why Does JavaScript Behave So Weirdly?

To understand this, you have to go back to JavaScript’s roots.

Back in 1995, Brendan Eich built JavaScript in just 10 days as a scripting language to add interactivity to web pages. It was never designed for heavy computation or complex asynchronous operations. But the web kept growing, and JavaScript had to evolve.

The language had to handle events like user clicks, file loads, and network requests—things that take time. But it still needed to be responsive and run smoothly. So how did it manage that?

The answer lies in one beautiful (and slightly magical) concept:

✨ The Event Loop

That’s the secret sauce.

Let’s break it down:

let count = 0;

setTimeout(() => {
  count++;
  console.log(count);
}, 1000);

console.log(count); // Outputs: 0

You might expect this to wait a second, increment count, and then log it. But it doesn’t. console.log(count) runs first, printing 0, and the callback from setTimeout runs later.

What’s going on under the hood?


🔍 JavaScript’s Runtime Components

  1. Call Stack
    This is where functions are called and executed—top of the stack gets run first. It works in a last-in, first-out manner.

  2. Web APIs (or Environment APIs)
    These are provided by the browser (or Node.js), not JavaScript itself. Things like setTimeout, AJAX, or file system access are handled here.

  3. Event Queue
    This is where completed asynchronous tasks wait to be executed. It comes in two flavors:

    • Macro Task Queue: For setTimeout, setInterval, I/O, etc.

    • Micro Task Queue: For Promises, queueMicrotask, MutationObserver, etc. These are prioritized over macro tasks.


🧠 How It All Works Together

  1. JavaScript begins executing your code using the call stack.

  2. When it hits something asynchronous like setTimeout, it delegates the task to Web APIs.

  3. Once the timer completes, the callback is sent to the event queue.

  4. If the call stack is empty, the event loop pushes tasks from the micro task queue (if any) first, then the macro task queue, back onto the call stack to be executed.

This cycle repeats continuously.


🎵 Why Go Through All This Trouble?

Because this design allows JavaScript to:

  • Run on a single thread.

  • Maintain responsiveness.

  • Handle multiple asynchronous operations without blocking the main flow.

JavaScript essentially becomes an orchestration layer: managing what needs to happen and when, while making it feel like it's all happening in sync.


🚀 How Do You Use This?

If you’ve ever used:

  • async/await

  • setTimeout

  • Promises

  • fetch

...then you’re already taking advantage of the event loop. Behind the scenes, JS is juggling everything for you—so you can focus on writing logic instead of worrying about concurrency.


🧾 Conclusion

JavaScript’s behavior might seem weird at first, but it's actually a brilliant compromise between simplicity and power. With just a single-threaded model and an event loop, it’s able to handle everything from simple DOM events to complex network operations without breaking a sweat.

So next time someone tells you JS is synchronous and non-blocking, just smile—and show them the magic of the event loop.

0
Subscribe to my newsletter

Read articles from Agnibha Chatterjee directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Agnibha Chatterjee
Agnibha Chatterjee