Event Loop and Concurrency
Understanding JavaScript's event loop and concurrency model
Challenge
Section titled “Challenge”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');Explanation
Section titled “Explanation”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.
Solution
Section titled “Solution”console.log('Start'); // 1
setTimeout(() => console.log('setTimeout 1'), 0); // Macrotask queuePromise.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 2Understanding Task Queues
Section titled “Understanding Task Queues”// 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 1Event Loop Steps
Section titled “Event Loop Steps”// 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, 8Complex Real-World Scenario
Section titled “Complex Real-World Scenario”const btn = document.querySelector('button');
// Multiple layers of queuesbtn.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 - MacrotaskPractical Applications
Section titled “Practical Applications”// 1. Debouncing with microtasksfunction 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 updateslet 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());}
// Usagebatch(() => console.log('A'));batch(() => console.log('B'));batch(() => console.log('C'));// All executed together after current sync codePerformance Considerations
Section titled “Performance Considerations”// ❌ Bad: Forcing layout thrashingfor (let i = 0; i < elements.length; i++) { elements[i].style.width = elements[i].offsetWidth + 10 + 'px'; // Read, then write}
// ✅ Good: Batch reads and writesconst widths = elements.map(el => el.offsetWidth); // Read all firstelements.forEach((el, i) => { el.style.width = widths[i] + 10 + 'px'; // Write all at once});
// ✅ Better: Use requestAnimationFrame for DOM updatesfunction updateLayout() { requestAnimationFrame(() => { elements.forEach(el => { el.style.width = el.offsetWidth + 10 + 'px'; }); });}Interview Question: Output Prediction
Section titled “Interview Question: Output Prediction”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