Ads and ad measurement. Disabled until you allow it.
ABOUT SITE
All open site tabs
JavaScript · Syntax · Advanced
The event loop
10 tasks
How JavaScript handles concurrency with one thread and one queue at a time. Covers the call stack, task queue, microtask queue, and the order of `setTimeout` vs `Promise.resolve`.
Quick topic start and explanations before exercises (exercises below):
**Why JavaScript is single-threaded**
JavaScript was designed for the browser where it touches the DOM. If two threads could modify the DOM simultaneously, you'd need locks and the mental overhead that comes with them. Single-threading avoids all that — there are no data races, no deadlocks, no need for mutexes. Concurrency is achieved differently: by *not blocking* on I/O.
When the browser makes a network request, it hands it to the OS and *registers a callback* to be called when the data arrives. Meanwhile the JS thread is free to handle UI events, run animations, respond to user input. This is *non-blocking I/O*.
**The event loop model**
There is one call stack. It runs one frame at a time to completion. Callbacks and microtasks queue up and are processed between stack frames:
```
Call Stack Microtask Queue Task Queue (Macrotasks)
────────── ─────────────── ──────────────────────
│ fn() │ ←── │ .then() │ ←─ │ setTimeout cb │
│ main() │ │ await │ │ I/O callback │
────────── │ queueMicro │ │ setInterval cb │
─────────────── ──────────────────────
```
**The full loop — step by step:**
1. Run the current task (synchronous code) to completion
2. Drain the microtask queue completely (new microtasks added during drain also run *before* the next macrotask)
3. Render (browser only, if a frame is due)
4. Pick the next macrotask from the task queue
5. Repeat
**Macrotasks** (task queue): `setTimeout`, `setInterval`, I/O callbacks, UI events, `MessageChannel`
**Microtasks** (microtask queue): Promise `.then`/`.catch`/`.finally`, `await` resume, `queueMicrotask()`, `MutationObserver`
**Quick reference**
| What | Queue | Priority |
|---|---|---|
| Synchronous code | current task | highest |
| `Promise.then/catch/finally` | microtask | before next macrotask |
| `await` resume point | microtask | before next macrotask |
| `queueMicrotask()` | microtask | before next macrotask |
| `MutationObserver` | microtask | before next macrotask |
| `setTimeout(fn, 0)` | macrotask | after all microtasks |
| `setInterval` | macrotask (recurring) | after all microtasks |
| `requestAnimationFrame` | before-paint task | browser only, ~16ms |
| I/O callbacks | macrotask | after all microtasks |
**Pitfalls**
**1. Heavy sync work blocks everything:**
```js
// BAD — freezes the tab for the duration of the loop
function processAll(items) {
items.forEach(item => heavyWork(item));
}
// BETTER — yield to the event loop between chunks
async function processAll(items) {
for (let i = 0; i < items.length; i++) {
heavyWork(items[i]);
if (i % 100 === 0) await new Promise(r => setTimeout(r, 0));
}
}
```
**2. Infinite microtask recursion starves macrotasks:**
```js
// Freezes the tab — microtasks run before the next macrotask
// so the event loop never moves on
function loop() { Promise.resolve().then(loop); }
loop();
// Safe equivalent using macrotask:
function loop() { setTimeout(loop, 0); }
```
**3. `setTimeout(fn, 0)` is not zero delay:**
The delay is a *minimum*, not a guarantee. The callback fires only when the call stack is empty and the task queue is reached. Under heavy load this could be many milliseconds later.
**4. `await` inside a loop:**
```js
// Sequential — each fetch waits for the previous:
for (const url of urls) {
const data = await fetch(url);
}
// Concurrent — all fetches start at the same time:
const results = await Promise.all(urls.map(url => fetch(url)));
```
**Web Workers — true parallelism for CPU work**
When you need real CPU parallelism (image processing, parsing), use a Worker. Workers run in a separate thread with their own event loop and communicate via `postMessage`:
```js
// main.js
const worker = new Worker('./worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = e => console.log('result:', e.data);
// worker.js
self.onmessage = e => {
const result = heavyCPUWork(e.data.data); // doesn't block main thread
self.postMessage(result);
};
```
Without running it, predict the exact output order of this code:
```js
function a() { console.log('a'); b(); }
function b() { console.log('b'); }
console.log('start');
a();
console.log('end');
```
Write the expected output as comments, then verify by running it.
function a() { console.log('a'); b(); }
function b() { console.log('b'); }
console.log('start');
a();
console.log('end');
// Expected output (write here before running):
// ???
Solution
function a() { console.log('a'); b(); }
function b() { console.log('b'); }
console.log('start');
a();
console.log('end');
// Output: start, a, b, end
// Stack grows: main -> a -> b, then unwinds: b returns, a returns, main continues
Predict the output order, then explain WHY `'2'` appears after `'3'` even though the delay is 0:
```js
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
```
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// Output: 1, 3, 2
//
// setTimeout schedules a macrotask in the task queue.
// The current synchronous code (1, then 3) finishes first.
// Only then does the event loop pick up the queued callback (2).
// '0ms delay' means 'not before 0ms AND after the current task ends'.
Predict the output. Then compare with the `setTimeout(0)` example from the previous exercise and explain the difference:
```js
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');
```
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');
// Output: ???
// How is this different from setTimeout(fn, 0)?
Solution
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');
// Output: 1, 3, 2
// Same order as setTimeout example, but the mechanism differs.
//
// Promise .then() is a MICROTASK.
// Microtasks run after the current task but BEFORE the next macrotask.
// With setTimeout: sync -> macrotask
// With .then(): sync -> microtask -> (then macrotask if any)
// The observable order is the same here, but microtasks have higher priority.
async function main() {
console.log('async start');
await Promise.resolve();
console.log('async end');
}
console.log('before');
main();
console.log('after');
// Output: before, async start, after, async end
//
// main() runs synchronously until the first 'await'.
// 'await' suspends main() and schedules the rest as a microtask.
// Execution returns to the call site: 'after' runs.
// Then the microtask queue drains: 'async end' runs.
Use `queueMicrotask` to schedule a callback and predict where it appears relative to sync code and `setTimeout(0)`:
```js
setTimeout(() => console.log('timeout'), 0);
queueMicrotask(() => console.log('microtask'));
console.log('sync');
```
setTimeout(() => console.log('timeout'), 0);
queueMicrotask(() => console.log('microtask'));
console.log('sync');
// Output: sync, microtask, timeout
//
// queueMicrotask() schedules a microtask, just like Promise.resolve().then().
// Microtasks run before any macrotask, so 'microtask' beats 'timeout'.
Write a function `blockFor(ms)` that synchronously blocks the main thread for `ms` milliseconds using a busy loop. Schedule a `setTimeout` with 50ms, then call `blockFor(200)`. Observe that the timeout fires late — at ~200ms, not 50ms — and explain why.
function blockFor(ms) {
// busy loop: block the thread for ms milliseconds
}
const t0 = Date.now();
setTimeout(() => {
console.log(`Fired after ${Date.now() - t0}ms`); // expect ~200ms, not 50
}, 50);
blockFor(200);
console.log(`Sync done at ${Date.now() - t0}ms`);
Solution
function blockFor(ms) {
const end = Date.now() + ms;
while (Date.now() < end) {}
}
const t0 = Date.now();
setTimeout(() => {
console.log(`Fired after ${Date.now() - t0}ms`); // ~200ms
}, 50);
blockFor(200);
console.log(`Sync done at ${Date.now() - t0}ms`); // ~200ms
// While blockFor() is running, the event loop is stuck.
// No timers, no network, no user events can fire — they all queue up.
// The 50ms timeout can only fire after the 200ms block releases the thread.
Predict the output. Pay attention to the microtask that is scheduled INSIDE the first `setTimeout` callback:
```js
setTimeout(() => {
console.log('macro 1');
Promise.resolve().then(() => console.log('micro inside'));
}, 0);
setTimeout(() => console.log('macro 2'), 0);
```
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.