-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
345 lines (319 loc) · 27.5 KB
/
Copy pathindex.html
File metadata and controls
345 lines (319 loc) · 27.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loopscope — JavaScript Event Loop Visualizer | Call Stack, Microtasks & Macrotasks Explained</title>
<meta name="description" content="Paste any JavaScript and watch it actually run through the Call Stack, Web APIs, Microtask Queue, and Callback Queue in real time. Includes a full beginner-to-advanced guide to the JavaScript event loop.">
<meta name="keywords" content="javascript event loop, event loop visualizer, call stack, microtask queue, macrotask queue, web apis, async await, promises, settimeout, queueMicrotask, javascript concurrency model">
<meta name="robots" content="index, follow">
<meta name="theme-color" content="#0B0E14">
<meta property="og:type" content="website">
<meta property="og:title" content="Loopscope — JavaScript Event Loop Visualizer">
<meta property="og:description" content="Paste any JavaScript and watch it run through the Call Stack, Web APIs, Microtask Queue, and Callback Queue — plus a full guide to how the event loop actually works.">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Loopscope — JavaScript Event Loop Visualizer">
<meta name="twitter:description" content="Watch your own JavaScript run through the Call Stack, Web APIs, Microtask Queue, and Callback Queue — with a complete beginner-to-advanced guide.">
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/8.11.3/acorn.min.js"></script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "SoftwareApplication",
"name": "Loopscope",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Any (runs in browser)",
"description": "An interactive JavaScript event loop visualizer that interprets pasted code and animates its execution through the Call Stack, Web APIs, Microtask Queue, and Callback Queue.",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
},
{
"@type": "TechArticle",
"headline": "How the JavaScript Event Loop Works",
"about": "JavaScript event loop, call stack, microtask queue, macrotask queue, async/await, promises",
"proficiencyLevel": "Beginner to Advanced"
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is the JavaScript event loop?",
"acceptedAnswer": { "@type": "Answer", "text": "The event loop is the mechanism that lets single-threaded JavaScript handle asynchronous work. It continuously checks whether the call stack is empty, and if it is, it moves queued callbacks — microtasks first, then one macrotask — onto the stack to run." }
},
{
"@type": "Question",
"name": "Do microtasks or macrotasks run first?",
"acceptedAnswer": { "@type": "Answer", "text": "Microtasks always run first. After every macrotask (like a setTimeout callback) finishes, the event loop fully drains the microtask queue — running every pending Promise reaction and queueMicrotask callback, including new ones they schedule — before it starts the next macrotask." }
},
{
"@type": "Question",
"name": "Is setTimeout(fn, 0) really immediate?",
"acceptedAnswer": { "@type": "Answer", "text": "No. setTimeout(fn, 0) still has to wait for the current call stack to finish and for the microtask queue to fully drain, so it always runs after any synchronous code and any pending promises, even ones scheduled after it." }
},
{
"@type": "Question",
"name": "How does async/await relate to the event loop?",
"acceptedAnswer": { "@type": "Answer", "text": "async/await is syntax sugar over promises. Each await pauses the async function and hands control back to the caller immediately, returning a pending promise. When the awaited value settles, the rest of the function resumes as a microtask." }
},
{
"@type": "Question",
"name": "Why can a JavaScript app freeze even though it's asynchronous?",
"acceptedAnswer": { "@type": "Answer", "text": "Because there is only one call stack. Any synchronous code that runs long — a big loop, heavy computation — blocks that single thread completely, so no timers, promise callbacks, UI events, or rendering can happen until it finishes." }
}
]
}
]
}
</script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="app-shell" id="top">
<header>
<a class="brand" href="#top">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9" stroke="#5B9DFF" stroke-width="2"/>
<path d="M12 7v5l3.5 2" stroke="#5B9DFF" stroke-width="2" stroke-linecap="round"/>
</svg>
<div>
<div class="brand-name">Loopscope</div>
<div class="brand-sub">JavaScript event loop visualizer</div>
</div>
</a>
<div class="spacer"></div>
<nav aria-label="Page sections">
<a href="#what-is-event-loop">Learn</a>
<a href="#faq">FAQ</a>
</nav>
<select id="sampleSelect" aria-label="Load a sample script"></select>
<button id="runBtn" class="primary">▶ Run & Trace</button>
</header>
<main>
<div class="col-editor">
<div class="editor-toolbar">
<span style="font-size:12px;color:var(--text-dim);">script.js</span>
<span id="customBadge">custom code</span>
<div class="grow"></div>
<button id="clearBtn" class="icon" title="Clear editor">Clear</button>
</div>
<div class="editor-wrap">
<div class="line-numbers" id="lineNumbers">1</div>
<div class="gutter-highlight" id="gutterHighlight"></div>
<textarea id="codeInput" spellcheck="false" aria-label="JavaScript code editor"></textarea>
</div>
<div class="console-panel">
<div class="console-head">Console output</div>
<div class="console-body" id="consoleBody"></div>
</div>
</div>
<div class="col-viz">
<div class="viz-grid">
<div class="panel" id="panel-stack">
<div class="panel-head"><div class="dot"></div><div class="panel-title">Call Stack</div><div class="panel-sub">sync execution</div></div>
<div class="panel-body stack" id="stackBody"><div class="empty-hint">empty</div></div>
</div>
<div class="panel" id="panel-webapi">
<div class="panel-head"><div class="dot"></div><div class="panel-title">Web APIs</div><div class="panel-sub">timers · network</div></div>
<div class="panel-body" id="webapiBody"><div class="empty-hint">nothing pending</div></div>
</div>
<div class="panel" id="panel-micro">
<div class="panel-head"><div class="dot"></div><div class="panel-title">Microtask Queue</div><div class="panel-sub">promises · queueMicrotask</div></div>
<div class="panel-body" id="microBody"><div class="empty-hint">empty</div></div>
</div>
<div class="panel" id="panel-macro">
<div class="panel-head"><div class="dot"></div><div class="panel-title">Callback Queue</div><div class="panel-sub">setTimeout · setInterval</div></div>
<div class="panel-body" id="macroBody"><div class="empty-hint">empty</div></div>
</div>
</div>
<div class="legend">
<span><b>Order:</b></span>
<span><span class="sw" style="background:var(--stack)"></span>Call Stack runs first, always</span>
<span><span class="sw" style="background:var(--micro)"></span>Microtasks drain fully before anything else</span>
<span><span class="sw" style="background:var(--macro)"></span>then one Callback Queue task runs</span>
<span><span class="sw" style="background:var(--webapi)"></span>repeat</span>
</div>
<div class="transport">
<button id="stepBackBtn" class="icon" disabled aria-label="Step back">⏮</button>
<button id="playBtn" class="icon" disabled aria-label="Play">▶</button>
<button id="stepFwdBtn" class="icon" disabled aria-label="Step forward">⏭</button>
<span class="speed-label">speed</span>
<input type="range" id="speedRange" min="60" max="1200" value="450" aria-label="Playback speed">
<div class="texts">
<div class="step-desc" id="stepDesc">Write or pick some JavaScript, then hit Run & Trace.</div>
<div class="step-why" id="stepWhy"></div>
</div>
<div class="step-count" id="stepCount">0 / 0</div>
</div>
<details class="notes">
<summary>What this does & current limits</summary>
<ul>
<li>Your code runs through a real interpreter built for this tool (not the browser's native scheduler), so the whole run is computed instantly, then replayed step-by-step so you can watch it.</li>
<li>Supported: closures, all control flow, destructuring (objects/arrays, with defaults and rest), spread/rest, template literals, classes (constructors, methods, getters/setters, static members, <code>extends</code>/<code>super</code>), labeled <code>break</code>/<code>continue</code>, real Promises (then/catch/finally/all/race/allSettled/any), async/await, setTimeout/setInterval/clearTimeout/clearInterval, queueMicrotask, and the full native standard library (Math, JSON, Array/String/Object methods, etc).</li>
<li><code>fetch</code> is mocked with a simulated network delay — there's no real networking here, just a Promise that resolves after a virtual delay, so its position in the event loop is still accurate.</li>
<li>Not supported: generators, private class fields (<code>#x</code>), decorators, modules, and DOM APIs.</li>
<li><code>setInterval</code> auto-stops after 8 firings so traces stay finite.</li>
</ul>
</details>
</div>
</main>
</div>
<div class="scroll-cue"><a href="#what-is-event-loop">↓ New to the event loop? Read the full guide below</a></div>
<div class="docs-wrap">
<article class="docs" id="what-is-event-loop">
<h1>How the JavaScript Event Loop Actually Works</h1>
<p class="lede">JavaScript runs on a single thread, yet it juggles timers, network requests, and user clicks without (usually) freezing. The event loop is the reason why. This guide walks through every moving part — the Call Stack, Web APIs, the Microtask Queue, the Callback Queue — from first principles up through the details that trip up experienced developers. Use the visualizer above to run any example live while you read.</p>
<nav class="toc" aria-label="Table of contents">
<div class="toc-title">On this page</div>
<ol>
<li><a href="#big-picture">The big picture</a></li>
<li><a href="#call-stack">The Call Stack</a></li>
<li><a href="#web-apis">Web APIs (and Node APIs)</a></li>
<li><a href="#macrotask-queue">The Callback Queue (macrotasks)</a></li>
<li><a href="#microtask-queue">The Microtask Queue</a></li>
<li><a href="#event-loop-algorithm">The event loop algorithm, step by step</a></li>
<li><a href="#step-types">Reading the visualizer's steps</a></li>
<li><a href="#async-await">async/await, demystified</a></li>
<li><a href="#rendering">Where rendering fits in</a></li>
<li><a href="#node-differences">Node.js vs. the browser</a></li>
<li><a href="#common-mistakes">Common mistakes & gotchas</a></li>
<li><a href="#glossary">Glossary</a></li>
<li><a href="#faq">FAQ</a></li>
</ol>
</nav>
<section id="big-picture">
<h2>The big picture</h2>
<p>JavaScript engines run your code on <b>one call stack</b>. There's no thread pool for your functions, no parallel execution of two lines of JS at once. So how does a web page fetch data, run a 3-second timer, and still respond to a click in the meantime?</p>
<p>The answer: JavaScript doesn't actually do the waiting itself. When you call <code>setTimeout</code>, <code>fetch</code>, or read a file in Node, the JS engine hands that work off to something outside itself — the browser's <b>Web APIs</b> (or Node's C++ APIs, backed by libuv) — and immediately moves on to the next line. When that background work finishes, it doesn't jump back into your running code (that would be true parallelism and would corrupt everything). Instead, it drops a message — a callback — into a queue and waits.</p>
<p>The <b>event loop</b> is a simple, tireless referee whose only job is: <i>"Is the call stack empty? If so, is there a queued callback ready to run? If so, put it on the stack."</i> That's it. Everything asynchronous in JavaScript — promises, timers, events, I/O — ultimately reduces to functions waiting in a queue for this referee to notice.</p>
<div class="callout"><b>Mental model:</b> the Call Stack is what's running <i>right now</i>. Web APIs are where async work happens <i>out of view</i>. The Microtask Queue and Callback Queue are waiting rooms. The event loop is the only thing that moves work from a waiting room onto the stage.</div>
</section>
<section id="call-stack">
<h2>The Call Stack</h2>
<p>The call stack is a simple LIFO (last-in, first-out) structure that tracks which function is currently executing and who called it. Every time a function is invoked, a new <b>frame</b> is pushed on top. When that function returns, its frame is popped off.</p>
<pre><code>function multiply(a, b) { return a * b; }
function square(n) { return multiply(n, n); }
function printSquare(n) { console.log(square(n)); }
printSquare(5);
// Stack grows: printSquare → square → multiply
// Stack shrinks: multiply returns → square returns → printSquare returns</code></pre>
<p>Nothing else can happen while there's anything on the call stack — no timer fires, no promise callback runs, no click handler executes, no repaint occurs. This is why a long synchronous loop <i>freezes</i> the page: the stack never empties, so the event loop never gets a turn.</p>
</section>
<section id="web-apis">
<h2>Web APIs (and Node APIs)</h2>
<p>Functions like <code>setTimeout</code>, <code>setInterval</code>, <code>fetch</code>, DOM event listeners, and geolocation aren't actually part of the JavaScript language — they're provided by the <i>host environment</i> (the browser, or Node's runtime). When you call one, the engine registers the request with the host and returns control to your code immediately; your call stack frame for <code>setTimeout(...)</code> pops right away, long before the timer has actually elapsed.</p>
<p>The host does the actual waiting — counting down a timer, listening on a socket, waiting for a file read — completely outside the JS thread. Once the work is done, the host doesn't run your callback directly either; it places it into the appropriate queue for the event loop to pick up later.</p>
</section>
<section id="macrotask-queue">
<h2>The Callback Queue (macrotasks)</h2>
<p>Also called the <b>task queue</b> or <b>macrotask queue</b>. This is where callbacks from <code>setTimeout</code>, <code>setInterval</code>, DOM events, and I/O completions land once their Web API work is finished. The event loop takes <b>one</b> task from here per loop iteration — never more — runs it to completion (which may itself push new things onto the stack, schedule new microtasks, or register new Web API work), and then checks the microtask queue again before touching this queue a second time.</p>
<p>Because only one macrotask runs per iteration, and each is followed by a full microtask drain, macrotasks are the "slow lane" — good for callbacks that can tolerate being deferred, and a natural point where the browser can sneak in a repaint.</p>
</section>
<section id="microtask-queue">
<h2>The Microtask Queue</h2>
<p>This is where <code>.then()</code>/<code>.catch()</code>/<code>.finally()</code> callbacks, <code>await</code> continuations, and <code>queueMicrotask()</code> callbacks land. The critical rule: <b>the microtask queue is always drained completely</b> before the event loop moves on — including any new microtasks scheduled by microtasks that are currently running. If a microtask schedules another microtask, that one runs too, before any macrotask gets a turn.</p>
<pre><code>Promise.resolve().then(() => {
console.log('first');
Promise.resolve().then(() => console.log('spawned by first'));
});
Promise.resolve().then(() => console.log('second'));
// Output: first, spawned by first, second
// — the microtask spawned by "first" still runs before "second"'s own
// turn was already queued, but strictly, all pending microtasks drain
// in FIFO order, and freshly-added ones join the same queue.</code></pre>
<p>This is why microtasks are the "fast lane": a chain of promises can, in principle, keep the event loop from ever reaching a macrotask (this is a real bug pattern — see <a href="#common-mistakes">Common mistakes</a>).</p>
</section>
<section id="event-loop-algorithm">
<h2>The event loop algorithm, step by step</h2>
<p>Put together, one full pass looks like this:</p>
<ol>
<li>Run all synchronous code from top to bottom (the initial script is itself treated like a task).</li>
<li>Call stack is now empty. Drain the microtask queue completely — run every pending microtask, including ones added while draining — until it's empty.</li>
<li>If the browser has scheduling headroom, it may perform a rendering update here (see <a href="#rendering">Where rendering fits in</a>).</li>
<li>Take the single oldest task from the callback queue (if any) and run it on the call stack.</li>
<li>Go back to step 2.</li>
</ol>
<p>This loop runs forever, as fast as it can when there's work, and idles when both queues and all pending Web API work are empty.</p>
</section>
<section id="step-types">
<h2>Reading the visualizer's steps</h2>
<p>When you click <b>Run & Trace</b> above, your code is executed once instantly against a virtual scheduler, producing a full timeline of discrete steps. Stepping through with the transport controls replays that timeline. Here's what each kind of step means:</p>
<table class="step-legend-table">
<thead><tr><th>Step</th><th>What happened</th></tr></thead>
<tbody>
<tr><td class="who"><span class="swatch" style="background:var(--stack)"></span>Call Stack push</td><td>A function call just started and was placed on top of the Call Stack. It runs before anything below it can continue.</td></tr>
<tr><td class="who"><span class="swatch" style="background:var(--stack)"></span>Call Stack pop</td><td>A function finished (hit <code>return</code> or ran out of statements) and was removed from the stack. Control returns to whoever called it.</td></tr>
<tr><td class="who"><span class="swatch" style="background:var(--webapi)"></span>Web APIs — added</td><td>An async operation (<code>setTimeout</code>, <code>setInterval</code>, <code>fetch</code>) was handed off to run outside the call stack.</td></tr>
<tr><td class="who"><span class="swatch" style="background:var(--macro)"></span>Callback Queue — added</td><td>A Web API finished its background work and moved its callback into the Callback Queue, where it waits its turn.</td></tr>
<tr><td class="who"><span class="swatch" style="background:var(--macro)"></span>Callback Queue — running</td><td>The Call Stack and Microtask Queue were both empty, so the event loop pulled the oldest callback here and started running it.</td></tr>
<tr><td class="who"><span class="swatch" style="background:var(--micro)"></span>Microtask Queue — added</td><td>A Promise settled (or <code>queueMicrotask</code> was called), scheduling a reaction callback.</td></tr>
<tr><td class="who"><span class="swatch" style="background:var(--micro)"></span>Microtask Queue — running</td><td>The event loop pulled the next microtask and ran it. This always happens before the next Callback Queue task.</td></tr>
</tbody>
</table>
<p>The current line of your code is highlighted in the editor at each step where that's meaningful (function calls, scheduling calls, console output), so you can follow along token-by-token.</p>
</section>
<section id="async-await">
<h2>async/await, demystified</h2>
<p><code>async</code>/<code>await</code> is syntax sugar over promises — it doesn't introduce a new concurrency model. An <code>async function</code> always returns a promise. Inside it, <code>await expr</code> does three things: it evaluates <code>expr</code>, wraps the result in a promise if it isn't already one, <b>suspends</b> the function and immediately returns control to the caller, and schedules the rest of the function to resume as a microtask once that promise settles.</p>
<pre><code>async function run() {
console.log('A');
await null; // suspends here — control returns to the caller now
console.log('B'); // resumes later, as a microtask
}
console.log('start');
run();
console.log('end');
// Output: start, A, end, B</code></pre>
<p>Notice that calling <code>run()</code> does not block — the synchronous part of the function (up to the first <code>await</code>) runs immediately, and everything after resumes later. This is exactly what you'd see if you rewrote it with raw <code>.then()</code> chains; <code>async</code>/<code>await</code> just reads top-to-bottom instead of nesting callbacks.</p>
</section>
<section id="rendering">
<h2>Where rendering fits in (browsers only)</h2>
<p>Browsers try to repaint the screen around 60 times per second, but a repaint can only happen when the call stack is empty — the same rule that governs everything else. In practice, the browser looks for an opportunity between macrotasks (roughly: after a task and its resulting microtasks are done) to run style/layout/paint if a frame is due. <code>requestAnimationFrame</code> callbacks are scheduled to run right before that paint step, making them the right tool for visual updates — unlike <code>setTimeout</code>, which has no awareness of the frame schedule at all.</p>
</section>
<section id="node-differences">
<h2>Node.js vs. the browser</h2>
<p>Node.js has its own event loop, implemented by <b>libuv</b>, and it's more elaborate than the browser's. Instead of one generic callback queue, Node cycles through named <b>phases</b> each iteration: <i>timers</i> (setTimeout/setInterval), <i>pending callbacks</i>, <i>poll</i> (I/O), <i>check</i> (<code>setImmediate</code>), and <i>close callbacks</i>. Microtasks (promises) and, uniquely to Node, <code>process.nextTick</code> callbacks are drained between every phase — and <code>process.nextTick</code> jumps the queue ahead of even regular promise microtasks. The high-level rule from this guide still holds — synchronous code first, then microtasks fully drained, then one macrotask-ish thing at a time — but the exact phase ordering is Node-specific and worth a dedicated read if you're debugging server-side timing bugs.</p>
</section>
<section id="common-mistakes">
<h2>Common mistakes & gotchas</h2>
<h3>Blocking the main thread</h3>
<p>A tight synchronous loop or heavy computation blocks everything — timers, promise callbacks, clicks, scrolling, painting — because none of it can run until the call stack empties. There's no such thing as a JS engine "interrupting" your running function to squeeze in a timer.</p>
<h3>Microtask starvation</h3>
<p>Because the microtask queue must fully drain before the next macrotask, a promise reaction that keeps re-scheduling itself (e.g. an unbounded <code>.then()</code> recursion) can starve macrotasks — and rendering — indefinitely, even though each individual microtask finishes quickly.</p>
<h3>Assuming <code>setTimeout(fn, 0)</code> means "right now"</h3>
<p>It means "as soon as possible, but strictly after the current synchronous code and every pending microtask." If you schedule a promise and a zero-delay timeout back to back, the promise callback always wins.</p>
<h3>Off-by-one thinking about <code>await</code></h3>
<p>Every <code>await</code>, even <code>await</code> on an already-resolved value, defers the rest of the function by at least one microtask turn. Code after <code>await somePromise</code> never runs synchronously with the code that called the async function, even if the promise was already settled.</p>
<h3>Forgetting timers keep objects alive</h3>
<p>An active <code>setInterval</code> (or an unresolved promise chain referencing a closure) keeps everything that closure captured reachable, which is a common source of memory leaks in long-running pages or servers.</p>
</section>
<section id="glossary">
<h2>Glossary</h2>
<dl class="glossary">
<dt>Call Stack</dt><dd>The single, LIFO structure tracking which function is currently running and its chain of callers.</dd>
<dt>Heap</dt><dd>The region of memory where objects, arrays, and functions are actually allocated; the stack holds references into it.</dd>
<dt>Web API</dt><dd>Functionality provided by the browser (or Node runtime) outside the JS language itself — timers, network, DOM events, file I/O.</dd>
<dt>Task / Macrotask</dt><dd>A unit of work queued in the Callback Queue — one runs per event loop iteration.</dd>
<dt>Microtask</dt><dd>A higher-priority queued callback (promise reactions, <code>queueMicrotask</code>) that fully drains before the next macrotask.</dd>
<dt>Event Loop</dt><dd>The process that repeatedly checks whether the call stack is empty and, if so, moves the next queued callback onto it.</dd>
<dt>Starvation</dt><dd>When one queue (typically microtasks) keeps generating more work for itself, indefinitely delaying another queue's turn.</dd>
<dt>Blocking</dt><dd>Running synchronous code long enough that the call stack can't empty, freezing all async work and rendering.</dd>
</dl>
</section>
<section id="faq">
<h2>Frequently asked questions</h2>
<div class="faq-item"><h3>What is the JavaScript event loop?</h3><p>It's the mechanism that lets single-threaded JavaScript handle asynchronous work: it continuously checks whether the call stack is empty, and when it is, moves queued callbacks — all pending microtasks first, then a single macrotask — onto the stack to run.</p></div>
<div class="faq-item"><h3>Do microtasks or macrotasks run first?</h3><p>Microtasks always run first, and completely — every pending promise reaction and <code>queueMicrotask</code> callback, including new ones scheduled while draining — before the event loop is allowed to start the next macrotask.</p></div>
<div class="faq-item"><h3>Is <code>setTimeout(fn, 0)</code> really immediate?</h3><p>No. It still waits for the current call stack to finish and for the microtask queue to fully drain, so it always runs after synchronous code and any pending promises — even ones scheduled after it.</p></div>
<div class="faq-item"><h3>How does async/await relate to the event loop?</h3><p>It's syntax sugar over promises. Each <code>await</code> pauses the function and immediately returns control to the caller with a pending promise; when the awaited value settles, the rest of the function resumes as a microtask.</p></div>
<div class="faq-item"><h3>Why can a JavaScript app freeze even though it's "asynchronous"?</h3><p>Because there's only one call stack. Long-running synchronous code blocks it completely, so no timers, promise callbacks, UI events, or rendering can happen until it finishes.</p></div>
</section>
<p style="margin-top:48px;"><a href="#top" style="color:var(--stack);text-decoration:none;">↑ Back to the visualizer</a></p>
</article>
</div>
<footer>
Loopscope — an open-source JavaScript event loop visualizer. No tracking, no accounts, runs entirely in your browser.
</footer>
<script src="script.js"></script>
</body>
</html>