DEV Community

Sai Swaroop Bijinapalli
Sai Swaroop Bijinapalli

Posted on

JS Eventloop & Concurrency Model

๐Ÿš€ JavaScript Event Loop & Concurrency Model: A Beginner's Guide

"JavaScript is single-threaded, yet it can handle timers, API requests, user interactions, and asynchronous tasks without freezing the application. How is that possible? The answer lies in the Event Loop and JavaScript's Concurrency Model."

When I first started learning JavaScript, one question always confused me:

If JavaScript can execute only one task at a time, how does it perform multiple tasks like API calls, timers, and user interactions simultaneously?

The answer is JavaScript's Event Loop and Concurrency Model.

In this article, we'll understand how JavaScript manages asynchronous operations behind the scenes using simple explanations, diagrams, and practical examples.


๐Ÿ“š What You'll Learn

  • What is JavaScript's Concurrency Model?
  • Why JavaScript is Single-Threaded
  • Call Stack
  • Web APIs / Node APIs
  • Callback Queue (Macrotask Queue)
  • Microtask Queue
  • Event Loop
  • setTimeout() vs Promises
  • Cooperative Concurrency
  • Concurrency vs Parallelism

Let's begin!


๐Ÿงต JavaScript is Single-Threaded

JavaScript executes code using a single thread, meaning it can perform only one operation at a time.

Imagine a chef working alone in a kitchen.

If the chef starts cooking one dish, they cannot cook another dish until the first one is finished.

Similarly, JavaScript executes one statement at a time.

Example:

console.log("Task 1");
console.log("Task 2");
console.log("Task 3");
Enter fullscreen mode Exit fullscreen mode

Output

Task 1
Task 2
Task 3
Enter fullscreen mode Exit fullscreen mode

Everything runs sequentially.


๐Ÿค” Then How Does JavaScript Handle Multiple Tasks?

Modern applications need to:

  • Fetch data from servers
  • Wait for timers
  • Read files
  • Handle button clicks
  • Play videos

If JavaScript waited for every operation to finish before continuing, web pages would freeze.

Instead, JavaScript uses a Concurrency Model.


โš™๏ธ What is the Concurrency Model?

The Concurrency Model allows JavaScript to manage multiple tasks efficiently without executing them simultaneously on the main thread.

JavaScript delegates time-consuming operations to the browser (Web APIs) or Node.js (Node APIs). While those operations run outside the JavaScript engine, the main thread continues executing other code.

When the asynchronous task finishes, its callback is queued and executed later by the Event Loop.


๐Ÿงฑ Call Stack

What is the Call Stack?

The Call Stack is a data structure that keeps track of function execution.

It follows the LIFO (Last In, First Out) principle.

Whenever a function is called:

  • It is pushed onto the stack.
  • When it finishes, it is popped off.

Example

function first() {
    console.log("First");
}

function second() {
    first();
    console.log("Second");
}

second();
Enter fullscreen mode Exit fullscreen mode

Output

First
Second
Enter fullscreen mode Exit fullscreen mode

Execution

Call Stack

โ†“

second()

โ†“

first()

โ†“

console.log()

โ†“

first() removed

โ†“

console.log()

โ†“

second() removed
Enter fullscreen mode Exit fullscreen mode

๐ŸŒ Web APIs / Node APIs

JavaScript itself cannot perform operations like timers or network requests.

These features are provided by the environment.

Browser Web APIs

  • setTimeout()
  • setInterval()
  • fetch()
  • DOM Events
  • localStorage
  • Geolocation

Node.js APIs

  • File System (fs)
  • HTTP Server
  • Streams
  • Timers
  • Process APIs

These APIs perform asynchronous work while JavaScript continues executing other code.

Example

console.log("Start");

setTimeout(() => {
    console.log("Timer Completed");
}, 2000);

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output

Start
End
Timer Completed
Enter fullscreen mode Exit fullscreen mode

Notice that JavaScript doesn't wait for two seconds.


๐Ÿ“ฅ Callback Queue (Macrotask Queue)

Once an asynchronous operation completes, its callback is placed inside the Callback Queue (also called the Macrotask Queue).

Examples of Macrotasks

  • setTimeout()
  • setInterval()
  • DOM Events
  • setImmediate() (Node.js)

Example

console.log("Start");

setTimeout(() => {
    console.log("Timeout");
}, 0);

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output

Start
End
Timeout
Enter fullscreen mode Exit fullscreen mode

Even though the timer delay is 0 milliseconds, the callback executes only after the Call Stack becomes empty.


โšก Microtask Queue

The Microtask Queue stores higher-priority asynchronous tasks.

Examples include:

  • Promise.then()
  • Promise.catch()
  • Promise.finally()
  • queueMicrotask()

Microtasks always execute before macrotasks.

Example

console.log("Start");

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

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output

Start
End
Promise
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”„ Event Loop

What is the Event Loop?

The Event Loop is a scheduler.

Its job is to continuously monitor the Call Stack.

Whenever the Call Stack becomes empty, it checks:

  1. Microtask Queue
  2. Callback Queue

The Event Loop first executes all microtasks.

Only after the Microtask Queue is empty does it execute one macrotask.


Event Loop Workflow

JavaScript Code
        โ”‚
        โ–ผ
   Call Stack
        โ”‚
        โ–ผ
Async Operation
        โ”‚
        โ–ผ
Web APIs / Node APIs
        โ”‚
        โ–ผ
 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚ Microtask Queue  โ”‚
 โ”‚ Promise.then()   โ”‚
 โ”‚ queueMicrotask() โ”‚
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚ Callback Queue   โ”‚
 โ”‚ setTimeout()     โ”‚
 โ”‚ DOM Events       โ”‚
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ”‚
        โ–ผ
    Event Loop
        โ”‚
        โ–ผ
    Call Stack
Enter fullscreen mode Exit fullscreen mode

๐Ÿ†š setTimeout() vs Promises

One of the most common interview questions is the execution order of setTimeout() and Promises.

Example

console.log("Start");

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

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

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output

Start
End
Promise
setTimeout
Enter fullscreen mode Exit fullscreen mode

Why Does Promise Execute First?

Let's understand the execution step by step.

Step 1

console.log("Start");
Enter fullscreen mode Exit fullscreen mode

Output

Start
Enter fullscreen mode Exit fullscreen mode

Step 2

setTimeout(...)
Enter fullscreen mode Exit fullscreen mode

The timer is registered with the Web API.


Step 3

Promise.resolve().then(...)
Enter fullscreen mode Exit fullscreen mode

The callback is placed inside the Microtask Queue.


Step 4

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output

End
Enter fullscreen mode Exit fullscreen mode

Step 5

The Call Stack becomes empty.

The Event Loop checks the Microtask Queue first.

Output

Promise
Enter fullscreen mode Exit fullscreen mode

Step 6

After all microtasks finish, the Event Loop executes the Callback Queue.

Output

setTimeout
Enter fullscreen mode Exit fullscreen mode

๐Ÿค Cooperative Concurrency

JavaScript uses cooperative concurrency.

This means:

  • Only one piece of JavaScript code executes at a time.
  • Tasks voluntarily yield control after they finish.
  • No task interrupts another task.

Example

console.log("Task 1");

setTimeout(() => {
    console.log("Task 2");
}, 0);

console.log("Task 3");
Enter fullscreen mode Exit fullscreen mode

Output

Task 1
Task 3
Task 2
Enter fullscreen mode Exit fullscreen mode

โšก Concurrency vs Parallelism

Many beginners think these terms mean the same thing, but they are different.

Concurrency

Managing multiple tasks by switching between them efficiently.

Example:

A chef preparing multiple dishes by moving between them.

Parallelism

Executing multiple tasks at the exact same time using multiple workers or CPU cores.

Example:

Three chefs cooking three different dishes simultaneously.


JavaScript and Parallelism

JavaScript's main thread is not parallel.

However, true parallelism can be achieved using:

  • Web Workers (Browser)
  • Worker Threads (Node.js)

These create separate threads for CPU-intensive work.


๐Ÿ“Š Microtask Queue vs Callback Queue

Feature Microtask Queue Callback Queue
Priority Higher Lower
Examples Promise.then(), catch(), finally(), queueMicrotask() setTimeout(), setInterval(), DOM Events
Execution Runs immediately after Call Stack is empty Runs after all Microtasks finish

๐Ÿ“ Key Takeaways

  • JavaScript is single-threaded.
  • The Call Stack executes synchronous code.
  • Web APIs and Node APIs handle asynchronous operations.
  • Completed asynchronous callbacks are placed into queues.
  • Microtask Queue has higher priority than the Callback Queue.
  • The Event Loop continuously checks the Call Stack and schedules tasks.
  • Promises always execute before setTimeout() callbacks because microtasks are processed first.
  • JavaScript achieves concurrency through the Event Loop, while true parallelism requires Web Workers or Worker Threads.

๐ŸŽฏ Conclusion

Understanding the Event Loop and JavaScript's Concurrency Model is essential for writing efficient asynchronous code. Concepts like the Call Stack, Web APIs, task queues, and the Event Loop explain why JavaScript can remain responsive even while handling timers, network requests, and user interactions.

Once you understand these fundamentals, topics like async/await, API calls, and modern frameworks such as React and Node.js become much easier to learn because they all build on the same asynchronous execution model.

Top comments (0)