Manifest Stack home

How JS Works

Part 1: Single-Threaded

JavaScript is single-threaded — it has one call stack and can only execute one piece of code at a time. There's no parallel execution of your JS code. This makes it simple to reason about, but raises a question: how does it handle slow tasks like network requests without freezing the browser?

The problem with blocking code

javascript
// If JS blocked on slow tasks, this would freeze the browser:
console.log("1. Start");

// Imagine this takes 5 seconds
let data = fetchDataSlowly(); // ← browser would freeze here!

console.log("2. Done"); // user can't click anything until this runs

Real browsers don't freeze like this because JavaScript uses asynchronous execution for slow operations — and the mechanism behind it is the event loop.

Part 2: The Event Loop

The event loop is the system that allows JavaScript to be non-blocking even though it's single-threaded. It has three components working together:

Call Stack

Where your JavaScript code actually runs. Functions are pushed on when called and popped off when they return. Only one function runs at a time.

Web APIs / Node APIs

Browser (or Node.js) provides these: setTimeout, fetch, DOM events. When you call them, the browser handles the slow work in the background — not JS.

Callback Queue

When an async task completes (timer fires, data arrives), its callback is placed here, waiting for the call stack to be empty.

The loop in action

javascript
console.log("1. Start");             // → Call Stack

setTimeout(() => {
    console.log("3. Timer done");    // → Web API waits 1s,
}, 1000);                            //   then → Callback Queue

console.log("2. End");               // → Call Stack

// Output:
// 1. Start
// 2. End
// 3. Timer done   ← runs after stack is empty

The event loop constantly checks: "Is the call stack empty? Is there anything in the callback queue?"If yes to both, it moves the next callback onto the stack and runs it.

Part 3: Synchronous vs Asynchronous

Synchronous — one after another

Each line waits for the previous one to finish before running. Predictable but can block if something is slow.

javascript
console.log("A");
console.log("B");
console.log("C");
// A → B → C, always in order

Asynchronous — don't wait

Slow operations are handed off. The rest of the code keeps running. The result comes back later via a callback, promise, or async/await.

javascript
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
// A → C → B
// Even with 0ms, B goes through the queue!

Promises and async/await — modern async

javascript
// Old way: callbacks (can get messy)
fetch(url, function(data) {
    parse(data, function(result) {
        save(result, function() { ... });
    });
});

// Modern way: async/await (reads like sync code)
async function loadUser() {
    const res    = await fetch("/api/user");   // wait here
    const data   = await res.json();           // then here
    console.log(data);                         // then here
}
// The await keyword pauses THIS function, but not the rest of the app