Understanding the JavaScript Event Loop: From Confusion to Clarity
Understanding the JavaScript Event Loop: From Confusion to Clarity
I remember the first time I wrote code that made a fetch request to an API and
then immediately tried to use the response. I was confused when it didn't
workβthe response was undefined. I thought JavaScript was broken, or maybe I
was using the wrong syntax.
// My naive attempt
const data = fetch('/api/users');
console.log(data); // undefined? What?
That's when I learned about asynchronous programming in JavaScript, and it
completely changed how I understood the language. But here's the
thingβunderstanding async code isn't just about learning async/await syntax.
To truly master JavaScript, you need to understand the event loop, the
mechanism that makes asynchronous operations possible in a single-threaded
environment.
In this post, we're going to explore the JavaScript event loop from the ground up. We'll start with why JavaScript is single-threaded, how the event loop works under the hood, and most importantly, what I learned the hard way about writing async code that doesn't block your application.
Intended audience: JavaScript developers who want to understand asynchronous
programming deeplyβfrom developers who've used async/await but don't know how
it works, to intermediate developers who want to understand the "why" behind
JavaScript's concurrency model and avoid common performance pitfalls.
Table of Contents
- Why JavaScript is Single-Threaded
- Understanding the Event Loop
- How the Event Loop Works
- The Call Stack, Web APIs, and Callback Queue
- Synchronous vs Asynchronous Code
- Common Async Patterns
- Why You Should Be Careful
- Microtasks vs Macrotasks
- Common Mistakes I Made
- Best Practices
- Key Takeaways
Why JavaScript is Single-Threaded
Before we dive into the event loop, let's understand a fundamental truth about JavaScript: it's single-threaded.
This means JavaScript can only execute one piece of code at a time. Unlike languages like Java or C++ that can spawn multiple threads to run code in parallel, JavaScript has one main thread that executes your code line by line.
But Wait, How Does It Handle Multiple Things?
Here's where it gets interesting. If JavaScript is single-threaded, how can it:
- Make API calls without blocking?
- Handle user clicks while processing data?
- Animate elements while fetching data?
The answer is the event loop. But before we get there, let's understand why JavaScript was designed this way.
Why Would They Design It This Way?
JavaScript was created in 1995 by Brendan Eich for Netscape Navigator. At the time, web browsers needed a simple scripting language that could:
- Run in a browser environment
- Manipulate the DOM safely
- Handle user interactions
If JavaScript had been multithreaded, imagine the chaos:
- Two threads trying to modify the same DOM element simultaneously
- Race conditions when handling user events
- Complex synchronization primitives (locks, mutexes) that most web developers wouldn't understand
By keeping JavaScript single-threaded, the designers made it:
- Simpler: No need to worry about thread synchronization
- Safer: DOM manipulation is always sequential
- Predictable: Code executes in a deterministic order
But this created a problem: what if you need to wait for something slow?
The Blocking Problem
When I was first learning JavaScript, I wrote code like this:
// This blocks the entire browser
function slowOperation() {
const start = Date.now();
while (Date.now() - start < 5000) {
// Wait 5 seconds
}
console.log('Done!');
}
slowOperation();
console.log('This waits 5 seconds before appearing');
If you run this in a browser, the entire page freezes for 5 seconds. The browser
can't respond to clicks, can't render updates, nothing. This is because the
single thread is busy executing the while loop.
This is the problem the event loop solves.
Understanding the Event Loop
The event loop is JavaScript's way of handling asynchronous operations without blocking the main thread. Think of it like a restaurant:
The Restaurant Analogy
Imagine a single-threaded restaurant with one waiter (the main thread). Here's how it works:
- The waiter takes orders (executes your code)
- The waiter gives orders to the kitchen (sends async operations to Web APIs)
- The waiter continues serving other tables (keeps executing code)
- When food is ready, the kitchen rings a bell (callback is added to the queue)
- The waiter picks up the food when they're free (callback is executed)
The key insight: the waiter doesn't wait at the kitchen. They keep working, and when the food is ready, they pick it up.
This is exactly how the event loop works. Let me show you:
console.log('1. Start');
setTimeout(() => {
console.log('2. Timeout callback');
}, 0);
console.log('3. End');
// Output:
// 1. Start
// 3. End
// 2. Timeout callback
Even though the timeout is set to 0 milliseconds, it executes after the
synchronous code finishes. This is the event loop in action.
How the Event Loop Works Under the Hood
The event loop has several components working together:
- Call Stack: Where your code executes
- Web APIs: Browser-provided APIs (setTimeout, fetch, DOM events)
- Callback Queue: Where callbacks wait to be executed
- Microtask Queue: High-priority callbacks (Promises, queueMicrotask)
Here's how they work together:
console.log('1. Synchronous');
setTimeout(() => {
console.log('2. setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise');
});
console.log('4. Synchronous');
// Output:
// 1. Synchronous
// 4. Synchronous
// 3. Promise
// 2. setTimeout
Wait, why does the Promise execute before setTimeout? This is where microtasks come in, but let's start with the basics.
π Step-by-Step Execution Flow
Let's trace through what happens when JavaScript executes this code:
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 1000);
console.log('End');
Visual Execution Timeline:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EXECUTION TIMELINE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
STEP 1: Initial Execution
βββββββββββββββββββββββββββ
Call Stack: Web APIs: Microtask Queue: Callback Queue:
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β console.log β β β β β β β
β ('Start') β β β β β β β
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β
βΌ (executes, prints "Start", pops)
Call Stack: Web APIs: Microtask Queue: Callback Queue:
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β setTimeout β β β β β β β
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β β
βΌ βΌ
(registers) (starts 1000ms timer)
β β
βΌ β
(pops) β
β
βΌ (after 1000ms)
ββββββββββββββββ
β Timer done! β
ββββββββ¬ββββββββ
β
βΌ
Callback Queue:
βββββββββββββββ
β setTimeout β
β callback β
βββββββββββββββ
Call Stack: Web APIs: Microtask Queue: Callback Queue:
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β console.log β β β β β β setTimeout β
β ('End') β β β β β β callback β
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β
βΌ (executes, prints "End", pops)
STEP 2: Call Stack Empty - Event Loop Takes Over
βββββββββββββββββββββββββββββββββββββββββββββββββ
Call Stack: Event Loop: Microtask Queue: Callback Queue:
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β β β Checking β β β β setTimeout β
β EMPTY ββββββββ Queues β β EMPTY β β callback β
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β² β
β β
β βΌ
β Process Macrotasks
β β
β βΌ
β Move to Call Stack
β β
ββββββββββββββββββββββ
Call Stack: Event Loop: Microtask Queue: Callback Queue:
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β setTimeout β β β β β β β
β callback β β β β EMPTY β β EMPTY β
βββββββββββββββ ββββββββββββ βββββββββββββββ βββββββββββββββ
β
βΌ (executes, prints "Timeout", pops)
FINAL OUTPUT:
Start
End
Timeout (appears after 1 second)
Step 1: Call Stack Execution
console.log('Start')β executes β prints "Start" β popssetTimeout(...)β executes β registers callback with Web API β popsconsole.log('End')β executes β prints "End" β pops
Step 2: Web API Processing
- setTimeout callback waits 1000ms
- After 1000ms, callback is moved to Callback Queue
Step 3: Event Loop
- Checks if Call Stack is empty
- If empty, takes callback from Callback Queue
- Pushes callback to Call Stack
- Callback executes β prints "Timeout"
This is the event loop's job: continuously check if the call stack is empty, and if so, move callbacks from the queue to the stack.
The Call Stack, Web APIs, and Callback Queue
Let's break down each component:
The Call Stack
The call stack is where JavaScript executes your code. It's a LIFO (Last In, First Out) data structureβlike a stack of plates.
function first() {
console.log('First');
second();
}
function second() {
console.log('Second');
third();
}
function third() {
console.log('Third');
}
first();
// Call Stack during execution:
// [third]
// [second, third]
// [first, second, third]
// [first, second]
// [first]
// []
When a function is called, it's pushed onto the stack. When it returns, it's popped off. The call stack is synchronousβit executes one thing at a time.
Web APIs
Web APIs are provided by the browser (or Node.js runtime). They include:
setTimeout/setIntervalfetch/XMLHttpRequest- DOM events (
addEventListener) requestAnimationFrame
These APIs run outside the JavaScript engine. When you call setTimeout,
you're telling the browser: "Hey, run this callback after 1000ms." The browser
handles the timing, not JavaScript.
// This doesn't block JavaScript
setTimeout(() => {
console.log('This runs later');
}, 1000);
// This code continues executing immediately
console.log('This runs now');
The Callback Queue
When a Web API finishes (like a timeout expiring or a fetch completing), it adds a callback to the Callback Queue (also called the Task Queue or Macrotask Queue).
The event loop continuously checks:
- Is the call stack empty?
- If yes, take the first callback from the queue
- Push it onto the call stack
- Execute it
console.log('1');
setTimeout(() => console.log('2'), 0);
setTimeout(() => console.log('3'), 0);
console.log('4');
// Output: 1, 4, 2, 3
// Both timeouts are queued, then executed in order
Synchronous vs Asynchronous Code
Understanding the difference between synchronous and asynchronous code is crucial.
Synchronous Code
Synchronous code executes immediately and blocks until it's done:
console.log('1');
console.log('2');
console.log('3');
// Output: 1, 2, 3 (in order, immediately)
Each line waits for the previous one to finish.
Asynchronous Code
Asynchronous code doesn't block. It schedules work to happen later:
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
console.log('3');
// Output: 1, 3, 2
The setTimeout callback doesn't execute immediatelyβit's scheduled to run
after the current code finishes.
Real-World Example: Fetching Data
Here's a practical example I encountered when building a dashboard:
// β This doesn't work as expected
let userData;
fetch('/api/user')
.then((response) => response.json())
.then((data) => {
userData = data;
});
console.log(userData); // undefined - fetch hasn't completed yet!
The fetch call is asynchronous. It doesn't block, so console.log executes
immediately, before the data arrives.
// β
This works correctly
fetch('/api/user')
.then((response) => response.json())
.then((data) => {
console.log(data); // Data is available here
});
Common Async Patterns in JavaScript
JavaScript has evolved several ways to handle asynchronous code:
1. Callbacks (The Old Way)
Callbacks were the original way to handle async operations:
setTimeout(() => {
console.log('Callback executed');
}, 1000);
Problem: Callback hell
// Callback hell - hard to read and maintain
getUser(userId, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
getReplies(comments[0].id, (replies) => {
console.log(replies); // Finally!
});
});
});
});
2. Promises (Better)
Promises provide a cleaner way to handle async operations:
fetch('/api/user')
.then((response) => response.json())
.then((user) => {
return fetch(`/api/posts/${user.id}`);
})
.then((response) => response.json())
.then((posts) => {
console.log(posts);
})
.catch((error) => {
console.error('Error:', error);
});
Better, but still can get verbose with multiple async operations.
3. Async/Await (Modern)
async/await makes async code look synchronous:
async function loadUserData() {
try {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
console.log(posts);
} catch (error) {
console.error('Error:', error);
}
}
Much cleaner! But remember: async/await is just syntactic sugar over
Promises. Under the hood, it still uses the event loop.
Why You Should Be Careful: Blocking the Event Loop
Here's the most important thing I learned: you can still block the event loop with synchronous code.
The Problem
Even though JavaScript has async capabilities, synchronous code still blocks. If you write a slow synchronous operation, nothing else can execute until it finishes.
// This blocks the entire browser for 5 seconds
function blockEventLoop() {
const start = Date.now();
while (Date.now() - start < 5000) {
// Busy waiting - blocks everything!
}
}
blockEventLoop();
console.log('This won't appear for 5 seconds');
Common Blocking Operations
I've made these mistakes:
1. Heavy Loops
// β Blocks the event loop
function processLargeArray(array) {
for (let i = 0; i < array.length; i++) {
// Heavy computation
const result = complexCalculation(array[i]);
}
}
// β
Break it up with setTimeout or use Web Workers
function processLargeArrayAsync(array) {
let index = 0;
function processChunk() {
const chunkSize = 100;
const end = Math.min(index + chunkSize, array.length);
for (let i = index; i < end; i++) {
complexCalculation(array[i]);
}
index = end;
if (index < array.length) {
setTimeout(processChunk, 0); // Yield to event loop
}
}
processChunk();
}
π Visual: Blocking vs Non-Blocking Processing
BLOCKING APPROACH (β):
ββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββ
β Process ALL items at once β
β βββββββββββββββββββββββββββββββββββββ β
β β [item1][item2][item3]...[item10000]β β
β βββββββββββββββββββββββββββββββββββββ β
β Browser: FROZEN β β
β User clicks: IGNORED β β
β UI updates: BLOCKED β β
β Duration: 5 seconds (feels like 50s) β
βββββββββββββββββββββββββββββββββββββββββββ
NON-BLOCKING APPROACH (β
):
ββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββ
β Process in chunks β
β ββββββββ ββββββββ ββββββββ ... β
β βchunk1ββ βchunk2ββ βchunk3ββ ... β
β ββββββββ ββββββββ ββββββββ β
β β β β β
β Yield Yield Yield β
β β β β β
β Browser: RESPONSIVE β
β
β User clicks: HANDLED β
β
β UI updates: RENDERED β
β
β Duration: 5 seconds (feels instant) β
βββββββββββββββββββββββββββββββββββββββββββ
Timeline Comparison:
ββββββββββββββββββββ
Blocking: [ββββββββββββββββββββ] 5s frozen
Non-blocking: [β][β][β][β][β][β][β] 5s responsive
β β β β β β β
Each chunk yields to event loop
2. Synchronous File Operations (Node.js)
// β Blocks in Node.js
const fs = require('fs');
const data = fs.readFileSync('large-file.txt'); // Blocks!
// β
Non-blocking
const fs = require('fs');
fs.readFile('large-file.txt', (err, data) => {
// Callback executes when file is read
});
3. Heavy DOM Manipulation
// β Can block rendering
function renderManyElements() {
for (let i = 0; i < 10000; i++) {
const div = document.createElement('div');
document.body.appendChild(div); // Blocks!
}
}
// β
Use requestAnimationFrame or break it up
function renderManyElementsAsync() {
let index = 0;
function renderChunk() {
const chunkSize = 100;
const end = Math.min(index + chunkSize, 10000);
for (let i = index; i < end; i++) {
const div = document.createElement('div');
document.body.appendChild(div);
}
index = end;
if (index < 10000) {
requestAnimationFrame(renderChunk);
}
}
renderChunk();
}
How to Identify Blocking Code
If your application:
- Freezes during operations
- Becomes unresponsive
- Drops frames in animations
- Can't handle user input
You likely have blocking code. Use browser DevTools Performance tab to identify long tasks.
Microtasks vs Macrotasks: Understanding Priority
Here's where it gets interesting. Not all callbacks are created equal.
Macrotasks (Callback Queue)
Macrotasks include:
setTimeout/setInterval- DOM events
setImmediate(Node.js)- I/O operations
Microtasks (Microtask Queue)
Microtasks include:
Promise.then()/Promise.catch()/Promise.finally()queueMicrotask()MutationObserver
The Key Difference: Execution Order
Microtasks have higher priority than macrotasks. The event loop processes all microtasks before moving to the next macrotask.
π Priority Visualization
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EXECUTION PRIORITY β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Priority Level 1 (Highest): SYNCHRONOUS CODE
βββββββββββββββββββββββββββββββββββββββββββββ
Call Stack executes immediately, line by line
Priority Level 2: MICROTASKS
βββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββ
β Microtask Queue β
β (Processed BEFORE macrotasks) β
β β
β β’ Promise.then() β
β β’ Promise.catch() β
β β’ Promise.finally() β
β β’ queueMicrotask() β
β β’ MutationObserver β
βββββββββββββββββββββββββββββββββββββββ
β
β ALL microtasks execute
β before ANY macrotask
βΌ
Priority Level 3: MACROTASKS
βββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββ
β Callback Queue β
β (Processed AFTER microtasks) β
β β
β β’ setTimeout() β
β β’ setInterval() β
β β’ DOM events β
β β’ I/O operations β
β β’ setImmediate() (Node.js) β
βββββββββββββββββββββββββββββββββββββββ
Visual Execution Pattern:
Code Execution Order:
βββββββββββββββββββββ
1. Synchronous code
β
2. ALL Microtasks (one by one)
β
3. ONE Macrotask
β
4. ALL Microtasks again (if any new ones)
β
5. NEXT Macrotask
β
... and so on
console.log('1. Start');
setTimeout(() => {
console.log('2. setTimeout (macrotask)');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise (microtask)');
});
console.log('4. End');
// Output:
// 1. Start
// 4. End
// 3. Promise (microtask runs first!)
// 2. setTimeout (macrotask runs after)
Why This Matters
This priority system ensures that Promise callbacks execute as soon as possible, which is important for maintaining predictable async behavior.
// This can be surprising
setTimeout(() => console.log('timeout'), 0);
Promise.resolve()
.then(() => {
console.log('promise 1');
return Promise.resolve();
})
.then(() => {
console.log('promise 2');
});
// Output:
// promise 1
// promise 2
// timeout
Even though the timeout was queued first, all Promise microtasks execute before it.
Real-World Impact
I once encountered a bug where I expected a setTimeout to run before a Promise
callback, but it didn't:
// My incorrect assumption
setTimeout(() => {
console.log('This should run first');
}, 0);
someAsyncFunction().then(() => {
console.log('This should run second');
});
// Actually: Promise runs first!
Understanding microtasks vs macrotasks helped me fix this.
Common Mistakes I Made
Here are mistakes I've made and what I learned:
Mistake 1: Assuming Async Code Executes Immediately
// β Wrong assumption
let data;
fetch('/api/data')
.then((response) => response.json())
.then((result) => {
data = result;
});
console.log(data); // undefined - fetch is still in progress!
Lesson: Async code doesn't block, but it also doesn't execute immediately. Always handle the result inside the callback/Promise.
Mistake 2: Blocking with Synchronous Loops
// β Blocks the event loop
function processItems(items) {
items.forEach((item) => {
heavyComputation(item); // Blocks!
});
}
// β
Yield to event loop periodically
async function processItemsAsync(items) {
for (const item of items) {
await new Promise((resolve) => setTimeout(resolve, 0));
heavyComputation(item);
}
}
Lesson: Break up heavy synchronous operations to keep the UI responsive.
Mistake 3: Not Understanding Microtask Priority
// β Unexpected behavior
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => {
console.log('promise');
// This runs BEFORE timeout, even though timeout was queued first
});
Lesson: Microtasks (Promises) always execute before macrotasks (setTimeout).
Mistake 4: Creating Too Many Microtasks
// β Can starve macrotasks
function createManyPromises() {
for (let i = 0; i < 1000; i++) {
Promise.resolve().then(() => {
// If this creates more promises, macrotasks never run
});
}
}
Lesson: Be mindful of microtask creationβthey can delay macrotasks significantly.
Best Practices: How to Leverage Async Code Effectively
Here's what I've learned about writing effective async code:
1. Use Async/Await for Readability
// β
Clean and readable
async function fetchUserData(userId) {
try {
const user = await fetch(`/api/users/${userId}`).then((r) => r.json());
const posts = await fetch(`/api/users/${userId}/posts`).then((r) =>
r.json(),
);
return { user, posts };
} catch (error) {
console.error('Failed to fetch user data:', error);
throw error;
}
}
2. Handle Errors Properly
// β
Always handle errors
async function loadData() {
try {
const data = await fetch('/api/data').then((r) => r.json());
return data;
} catch (error) {
// Log error, show user-friendly message, etc.
console.error('Error loading data:', error);
return null; // Or throw, depending on your needs
}
}
3. Don't Block the Event Loop
// β
Break up heavy operations
async function processLargeDataset(data) {
const chunkSize = 1000;
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
processChunk(chunk);
// Yield to event loop every chunk
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
4. Use Promise.all for Parallel Operations
// β
Fetch multiple things in parallel
async function loadDashboard() {
const [user, posts, notifications] = await Promise.all([
fetch('/api/user').then((r) => r.json()),
fetch('/api/posts').then((r) => r.json()),
fetch('/api/notifications').then((r) => r.json()),
]);
return { user, posts, notifications };
}
5. Understand When to Use Microtasks vs Macrotasks
// Use microtasks for immediate, high-priority callbacks
Promise.resolve().then(() => {
// Runs as soon as possible
});
// Use macrotasks for lower-priority, delayed callbacks
setTimeout(() => {
// Runs after current microtasks
}, 0);
Key Takeaways
Here's what you should remember about the JavaScript event loop:
- JavaScript is single-threaded: Only one piece of code executes at a time.
- The event loop enables async behavior: It continuously checks if the call stack is empty and moves callbacks from queues to the stack.
- Web APIs run outside JavaScript: Operations like
setTimeoutandfetchare handled by the browser/runtime, not JavaScript itself. - Microtasks have priority: Promise callbacks execute before
setTimeoutcallbacks, even if the timeout was queued first. - Synchronous code still blocks: Heavy loops or synchronous operations can freeze your application.
- Break up heavy operations: Use
setTimeout,requestAnimationFrame, or Web Workers to keep the UI responsive. - Async/await is syntactic sugar: It makes async code look synchronous, but it still uses Promises and the event loop under the hood.
- Understand execution order: Knowing when code executes helps you write predictable async code.
What's Next?
Now that you understand the event loop, here are some related topics to explore:
- Web Workers: True parallelism in JavaScript (separate threads)
- Async Generators: Combining generators with async/await
- Streams API: Handling large data asynchronously
- Service Workers: Background processing for web apps
The event loop is fundamental to JavaScript. Understanding it will help you:
- Write more performant code
- Debug async issues more effectively
- Make better architectural decisions
- Understand how frameworks like React handle updates
Remember: the event loop is JavaScript's way of handling concurrency in a single-threaded environment. Once you understand it, async JavaScript becomes much clearer.
π Complete Event Loop Summary
Here's a final visual summary of everything we've covered:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β THE COMPLETE EVENT LOOP PICTURE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββ
β Your Code β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Call Stack β ββββ Executes one at a time
β (Synchronous) β
ββββββββββ¬βββββββββ
β
ββββββββββββββ΄βββββββββββββ
β β
Async? β β Sync?
β β
βΌ βΌ
βββββββββββββββββββββ ββββββββββββββββββββ
β Web APIs β β Continue β
β (Browser handles) β β executing β
ββββββββββββ¬βββββββββββ ββββββββββββββββββββ
β
β When ready
β
ββββββββββββ΄βββββββββββ
β β
βΌ βΌ
βββββββββββββββ βββββββββββββββ
β Microtask β β Callback β
β Queue β β Queue β
β (Priority) β β (Lower) β
β β β β
β β’ Promises β β β’ setTimeoutβ
β β’ queueMicroβ β β’ DOM eventsβ
ββββββββ¬βββββββ ββββββββ¬βββββββ
β β
ββββββββββ¬ββββββββββ
β
βΌ
βββββββββββββββββ
β Event Loop β ββββ Continuously checks
β β
β 1. Stack β
β empty? β
β 2. Process β
β ALL β
β microtasksβ
β 3. Process β
β ONE β
β macrotask β
βββββββββ¬ββββββββ
β
βΌ
βββββββββββββββββ
β Call Stack β ββββ Back to execution
βββββββββββββββββ
KEY PRINCIPLES:
βββββββββββββββ
1. Single-threaded: One thing at a time
2. Non-blocking: Web APIs handle async
3. Priority: Microtasks > Macrotasks
4. Continuous: Event loop never stops
5. Yielding: setTimeout(0) gives control back
The event loop is the heart of JavaScript's async model. Understanding it helps you:
- Write non-blocking code
- Debug async issues
- Optimize performance
- Build responsive applications
Happy coding! π