Skip to content
Plurafy

Event Loop and Concurrency

Understanding JavaScript's event loop and concurrency model

Predict the order of output for this code:

console.log('Start');
setTimeout(() => console.log('setTimeout 1'), 0);
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
setTimeout(() => console.log('setTimeout 2'), 0);
console.log('End');

JavaScript is single-threaded but uses the event loop to handle asynchronous operations. Understanding the microtask and macrotask queues is crucial for predicting execution order.

console.log('Start'); // 1
setTimeout(() => console.log('setTimeout 1'), 0); // Macrotask queue
Promise.resolve()
.then(() => console.log('Promise 1')) // Microtask queue
.then(() => console.log('Promise 2')); // Microtask queue
setTimeout(() => console.log('setTimeout 2'), 0); // Macrotask queue
console.log('End'); // 2
// Output:
// Start
// End
// Promise 1
// Promise 2
// setTimeout 1
// setTimeout 2
// CALL STACK: Executes synchronous code
// MICROTASK QUEUE: Promises, MutationObserver, queueMicrotask
// MACROTASK QUEUE: setTimeout, setInterval, setImmediate, I/O
function task() {
console.log('Task 1');
Promise.resolve()
.then(() => console.log('Microtask 1'))
.then(() => console.log('Microtask 2'));
setTimeout(() => console.log('Macrotask 1'), 0);
console.log('Task 2');
}
task();
// Output:
// Task 1
// Task 2
// Microtask 1
// Microtask 2
// Macrotask 1
// 1. Execute all synchronous code (call stack)
console.log('1');
// 2. Execute all microtasks (Promise callbacks)
Promise.resolve().then(() => console.log('2'));
queueMicrotask(() => console.log('3'));
// 3. Execute one macrotask (setTimeout)
setTimeout(() => console.log('4'), 0);
// 4. Check for more microtasks (repeat steps 2-3)
Promise.resolve().then(() => console.log('5'));
setTimeout(() => {
console.log('6');
Promise.resolve().then(() => console.log('7'));
setTimeout(() => console.log('8'), 0);
}, 0);
// Output: 1, 2, 3, 5, 4, 6, 7, 8
const btn = document.querySelector('button');
// Multiple layers of queues
btn.addEventListener('click', () => {
console.log('Click handler - Macrotask');
Promise.resolve()
.then(() => console.log('Promise in click - Microtask'));
setTimeout(() => console.log('setTimeout in click - Macrotask'), 0);
});
Promise.resolve()
.then(() => console.log('Global promise - Microtask'));
setTimeout(() => console.log('Global setTimeout - Macrotask'), 0);
// When button is clicked:
// Output order:
// Click handler - Macrotask
// Global promise - Microtask (and Promise in click)
// Promise in click - Microtask
// Global setTimeout - Macrotask
// setTimeout in click - Macrotask
// 1. Debouncing with microtasks
function efficientDebounce(func, delay = 0) {
let timeout;
let lastArgs;
return function(...args) {
lastArgs = args;
clearTimeout(timeout);
// Use microtask for immediate processing
Promise.resolve().then(() => {
timeout = setTimeout(() => {
func.apply(this, lastArgs);
}, delay);
});
};
}
// 2. Batch updates
let batchQueue = [];
let batchScheduled = false;
function batch(fn) {
batchQueue.push(fn);
if (!batchScheduled) {
batchScheduled = true;
Promise.resolve().then(processBatch);
}
}
function processBatch() {
const queue = batchQueue;
batchQueue = [];
batchScheduled = false;
queue.forEach(fn => fn());
}
// Usage
batch(() => console.log('A'));
batch(() => console.log('B'));
batch(() => console.log('C'));
// All executed together after current sync code
// ❌ Bad: Forcing layout thrashing
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = elements[i].offsetWidth + 10 + 'px'; // Read, then write
}
// ✅ Good: Batch reads and writes
const widths = elements.map(el => el.offsetWidth); // Read all first
elements.forEach((el, i) => {
el.style.width = widths[i] + 10 + 'px'; // Write all at once
});
// ✅ Better: Use requestAnimationFrame for DOM updates
function updateLayout() {
requestAnimationFrame(() => {
elements.forEach(el => {
el.style.width = el.offsetWidth + 10 + 'px';
});
});
}
async function example() {
console.log('1');
await Promise.resolve();
console.log('2');
setTimeout(() => console.log('3'), 0);
console.log('4');
}
example();
console.log('5');
// What's the output order?
// Answer: 1, 5, 2, 4, 3