What is Event Loop?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Medium
#JavaScript #Asynchronicity #JS Basics

Brief Answer

Event Loop is a mechanism in JavaScript responsible for executing code, collecting and processing events, and executing subprograms. It allows JavaScript to be single-threaded while still performing asynchronous operations like setTimeout, Promise, fetch, and others.

Main components:

  • Call Stack — function call stack
  • Callback Queue — queue of callbacks
  • Microtask Queue — queue of microtasks
  • Web APIs — browser’s asynchronous APIs

Full Answer

Event Loop is a fundamental mechanism that makes asynchronous programming possible in JavaScript. Despite JavaScript being single-threaded, Event Loop allows it to perform non-blocking asynchronous operations.

How Event Loop Works

Event Loop operates in the following cycle:

  1. Executes all synchronous operations from the call stack
  2. Checks the microtask queue and executes all tasks from there
  3. Checks the callback queue and executes one task from there
  4. Repeats the cycle
console.log('1');
 
setTimeout(() => console.log('2'), 0);
 
Promise.resolve().then(() => console.log('3'));
 
console.log('4');
 
// Output: 1, 4, 3, 2

Main Components

1. Call Stack

The stack where function calls are stored:

function first() {
  console.log('first');  // 1. console.log('first') executes
  second();              // 2. second() call is added to the stack
}
 
function second() {
  console.log('second'); // 3. console.log('second') executes
  third();               // 4. third() call is added to the stack
}
 
function third() {
  console.log('third');  // 5. console.log('third') executes
}                        // 6. third() completes and is removed from the stack
                         // 7. second() completes and is removed from the stack
                         // 8. first() completes and is removed from the stack
 
first();                 // 0. first() call is added to the stack

2. Web APIs

Browser’s asynchronous APIs that handle timers, events, HTTP requests:

setTimeout(() => {
  console.log('Timer completed');
}, 1000);
 
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));

3. Callback Queue

Queue for tasks like setTimeout, setInterval, click events:

setTimeout(() => {
  console.log('From callback queue');
}, 0);

4. Microtask Queue

Queue for Promise, queueMicrotask:

Promise.resolve().then(() => {
  console.log('From microtask queue');
});

Execution Priorities

The microtask queue has higher priority:

console.log('1');
 
setTimeout(() => console.log('2'), 0);
 
Promise.resolve().then(() => console.log('3'));
 
Promise.resolve().then(() => console.log('4'));
 
setTimeout(() => console.log('5'), 0);
 
console.log('6');
 
// Output: 1, 6, 3, 4, 2, 5

Practical Examples

Example with Promise and setTimeout

console.log('start');
 
setTimeout(() => console.log('setTimeout'), 0);
 
Promise.resolve()
  .then(() => console.log('promise1'))
  .then(() => console.log('promise2'));
 
console.log('end');
 
// Output: start, end, promise1, promise2, setTimeout

Example with Nested Promises

Promise.resolve()
  .then(() => {
    console.log('promise1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('promise2');
  });
 
Promise.resolve().then(() => console.log('promise3'));
 
// Output: promise1, promise3, promise2

Example with async/await

async function example() {
  console.log('1');
  
  await Promise.resolve();
  
  console.log('2');
}
 
example();
console.log('3');
 
// Output: 1, 3, 2

Common Mistakes

1. Misunderstanding Priorities

// ❌ Expectation: 1, 2, 3
// ✅ Reality: 1, 3, 2
console.log('1');
 
setTimeout(() => console.log('2'), 0);
 
Promise.resolve().then(() => console.log('3'));

2. Blocking Event Loop

// ❌ Bad - blocks Event Loop
function blockingOperation() {
  while (true) {
    // Infinite loop
  }
}
 
// ✅ Good - breaking into chunks
function nonBlockingOperation() {
  let i = 0;
  function processChunk() {
    const end = Math.min(i + 1000, data.length);
    for (; i < end; i++) {
      // Process data
    }
    if (i < data.length) {
      setTimeout(processChunk, 0);
    }
  }
  processChunk();
}

Best Practices

  1. Understand priorities — microtasks execute before callbacks
  2. Avoid blocking — break heavy operations into chunks
  3. Use Promises — for better asynchronous code management
  4. Optimize performance — minimize the number of tasks in queues

Key Features

  1. Single-threaded — JavaScript runs in a single thread
  2. Non-blocking I/O — asynchronous operations don’t block execution
  3. Microtask priority — microtask queue is completely emptied before the next task
  4. Predictability — strict order of task execution

Event Loop is the heart of asynchronous programming in JavaScript. Understanding how it works is crucial for writing efficient and predictable code, especially when working with timers, promises, network requests, and events.


Knowledge Check Tasks

Task 1

What will be output to the console?

console.log('A');
 
setTimeout(() => console.log('B'), 0);
 
Promise.resolve().then(() => console.log('C'));
 
console.log('D');
View answer

Answer: A, D, C, B

Explanation:

  1. console.log('A') — synchronous call, executes immediately
  2. setTimeout(() => console.log('B'), 0) — asynchronous task goes to the Callback Queue
  3. Promise.resolve().then(() => console.log('C')) — microtask goes to the Microtask Queue
  4. console.log('D') — synchronous call, executes immediately
  5. Event Loop checks the Microtask Queue and executes console.log('C')
  6. Event Loop checks the Callback Queue and executes console.log('B')

Task 2

What will be the order of output?

setTimeout(() => console.log('A'), 0);
 
Promise.resolve().then(() => {
  console.log('B');
  return Promise.resolve();
}).then(() => console.log('C'));
 
console.log('D');
View answer

Answer: D, B, C, A

Explanation:

  1. setTimeout(() => console.log('A'), 0) — task in the Callback Queue
  2. Promise.resolve().then(...) — microtask in the Microtask Queue
  3. console.log('D') — synchronous call
  4. Microtask executes: output ‘B’, creates a new microtask for output ‘C’
  5. Second microtask executes: output ‘C’
  6. Task from Callback Queue: output ‘A’

Task 3

What will this code output?

async function async1() {
  console.log('A');
  await async2();
  console.log('B');
}
 
async function async2() {
  console.log('C');
}
 
console.log('D');
 
setTimeout(() => console.log('E'), 0);
 
async1();
 
new Promise((resolve) => {
  console.log('F');
  resolve();
}).then(() => console.log('G'));
 
console.log('H');
View answer

Answer: D, A, C, F, H, G, B, E

Explanation:

  1. Synchronous calls execute in order: D, A, C, F, H
  2. All microtasks execute before tasks from the Callback Queue: G, B
  3. Tasks from the Callback Queue: E

Understanding these tasks demonstrates a deep knowledge of Event Loop operation, task priorities, and asynchronous programming in JavaScript.


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪