The JavaScript runtime at a glance
See the engine, the host, and the queues in one accurate picture so the call stack, Web APIs, Node.js APIs, tasks, microtasks, and event loop fit together.
- 01Draw the runtime mapTell apart the engine's call stack and heap, the host's extras, and the queues.
- 02Predict the order of outputExplain why code that runs now prints first, then microtasks, then timer tasks.
- 03Keep the page from freezingSpot code that keeps the call stack busy too long, and split it into small pieces.
The runtime map
Your JavaScript code never runs on its own. It always runs inside a program called a runtime. A browser is a runtime. Node.js is a runtime too. This lesson draws one map of a runtime, so you can see where each part of your code runs, and when.
A runtime has these parts:
- The engine reads your JavaScript and runs it.
- The host is the program around the engine, such as a browser or Node.js. It adds extras such as timers, the web page, files, and the network.
- The queues are waiting lines. A function that should run later waits in a queue.
- The event loop takes the next waiting function from a queue and gives it to the engine to run.
You have met some of these ideas before. The Engines, runtimes & hosts lesson showed that the engine and the host are two different programs. The Recursion lesson showed the call stack. The Objects & references lesson showed that two variables can point at one object. This lesson puts all of them on one map.
A JavaScript runtime is an engine that runs your code, plus a host that adds extras, queues where functions wait, and an event loop that decides when each waiting function runs.
const price = 3;console.log(price * 2);console.log(typeof setTimeout);Line 1 makes a variable, price, that holds 3. Line 2 multiplies it by 2 and prints 6. The engine does that work, because numbers and * are part of the JavaScript language. Line 3 uses typeof, which tells you what kind of value something is. It prints function, because the host gave the page a function called setTimeout.
price * 2. The host is the program around the engine, here a browser. It adds extras such as setTimeout, document, and fetch.Picture a restaurant with one chef. The chef can cook only one order at a time. Helpers work around the chef: a kitchen timer beeps, a delivery driver comes back, a waiter brings a new order ticket. The chef always follows the same routine. First, finish the order in front of you. Next, handle every urgent note. Then take the next order ticket.
- In real life: One chef cooking one order
- In JavaScript: The engine running one piece of JavaScript at a time
- In real life: The order the chef is working on
- In JavaScript: The script or function call running right now
- In real life: The pantry where food is kept
- In JavaScript: The heap, where objects are kept
- In real life: Kitchen timers, delivery drivers, and waiters
- In JavaScript: Host extras such as timers,
fetch, the web page, or Node.js files - In real life: Order tickets and urgent notes
- In JavaScript: The task queue and the microtask queue
The rest of the lesson looks at each part of the runtime in turn, with its real name.
The engine: heap & call stack
STEP THROUGHThe engine is the part that understands JavaScript. Chrome uses an engine called V8. Firefox uses SpiderMonkey. Safari uses JavaScriptCore. The engine reads your code, makes values, does the math, calls functions, and looks after memory. Two places inside the engine matter for this map:
- The call stack is a list of the function calls that are running right now. The newest call sits on top, and the top call is the one that runs.
- The heap is the memory where objects and functions are kept. A variable does not hold the object itself. It holds a reference, which says where the object is.
You put each new plate on top of the pile. You also take plates off from the top. So the last plate you put on is the first one you take off.
- In real life: Put a plate on top
- In JavaScript: Call a function
- In real life: Take the top plate off
- In JavaScript: Return from that function
- In real life: You can't pull out a plate from the middle
- In JavaScript: A function waits while the function it called runs
The call stack works the same way. When one function calls another, the new call goes on top. The first function waits. It carries on only after the new call returns.
garnish() returns, its card comes off, and plate() carries on.Step through the call stack. The newest call always runs first, and it comes off the stack when it returns.
script
console.log("start " + label); garnish(label); console.log("finish " + label);} function garnish(label) { console.log("garnish " + label);} plate("tea");console.log("ready");The replay used no queue. A plain function call runs straight away, because the current line asked for it. Queues only matter for work that should run later.
Now look at memory. Objects are kept in the heap. A variable holds a reference to an object, not a copy of it. So two variables can refer to the same object.
const chef = { name: "Mina", tickets: 1 };const alias = chef;alias.tickets = alias.tickets + 1;console.log(chef.tickets);Console: not run yet
Before line 3 runs, chef and alias are two names that point at one object.
The drawing above is a simple model, not a picture of real memory. Real engines lay out memory in more complex ways, and they clean up objects that no one uses any more. You don't need those details here. You only need one rule: both names point at the same object, so a change made through one name shows up through the other.
const cart = { items: 1 };const sameCart = cart;sameCart.items = 2;console.log(cart.items);console.log(cart === sameCart);Line 1 makes an object with items: 1 and stores a reference to it in cart. Line 2 copies that reference into sameCart. It does not make a new object. Line 3 changes items to 2 through sameCart. Line 4 prints 2, because cart points at the same object. Line 5 prints true, because === finds that both names refer to one object.
cart and sameCart are two names for one object. A change made through one name shows up through the other, because there is only one object.If you need a real, separate copy, use structuredClone(cart). It works for plain data such as numbers, strings, arrays, and plain objects.
The host: Web APIs & Node.js APIs
SORTThe host is the program that the engine runs inside. The extras it adds are called APIs: named functions and objects your code can use. A browser's extras are called Web APIs.
- A browser lets your code use the web page, clicks and other events, timers, network requests, and saved data.
- Node.js lets your code use files, the running program, servers, and timers.
The table below compares the two hosts. Read each row as: "for this kind of job, a browser gives you this, and Node.js gives you that."
| What kind of job? | What a browser gives you | What Node.js gives you |
|---|---|---|
| Work with a page or the outside world | The web page, through document and its elements. | No web page. It runs on servers and in the terminal. |
| Talk to the network | fetch and WebSocket, with the browser's safety rules. | fetch in new versions, plus tools for building servers. |
| Run code later | setTimeout, setInterval, and requestAnimationFrame. | setTimeout, setInterval, and setImmediate. |
| Read files and the running program | Almost no direct access to files. | fs for files, process for the running program, and settings from the environment. |
| Decide what waits for what | Tasks, microtasks, and moments to redraw the page. | Tasks, microtasks, and an extra waiting line for process.nextTick. |
This split helps you avoid a common mistake: thinking that every name you can use is part of JavaScript itself.
Array.prototype.mapis part of the JavaScript language.document.querySelectoris a Web API from the browser.fs.readFileis an API from Node.js.
The engine runs the JavaScript around those calls. The host does the extra work, such as finding an element or reading a file.
- Call stack
- Heap for objects
console.log("hi")setTimeout(callback, 0)document.querySelector("button")2 + 3
Sort each card by who gives you the feature: the engine or the host.
Task & microtask queues
SORTA callback is a function you hand to someone else to call later. When a timer ends, the host has a callback to run. But the engine may still be busy with other code, and the host can't interrupt it. So the callback waits in a queue, a waiting line where the first one in is the first one out. There are two queues to know:
- The task queue holds bigger pieces of work: timer callbacks, most click and key handlers, and messages. The script itself also runs as one task.
- The microtask queue holds small, urgent callbacks. They run as soon as the call stack is empty, before the next task.
A promise is an object that stands for a result that may arrive later. In this lesson, you only need one fact about promises: a function you pass to a promise's .then method waits in the microtask queue. The function queueMicrotask puts a callback straight into the same queue.
console.log("start");queueMicrotask(() => { console.log("first"); queueMicrotask(() => console.log("second"));});setTimeout(() => console.log("timer"), 0);Line 1 prints start while the script runs. Line 2 puts a callback in the microtask queue. Line 6 starts a timer, so its callback will wait in the task queue. When the script ends, the microtask runs. It prints first and queues one more microtask. That new microtask also runs before any task, so second prints before timer.
- console.log in the script that is running
- setTimeout callback
- .then callback on a promise that has already finished
- click event handler
- queueMicrotask callback
- a plain function called by the current line
Place each line or callback where it waits before it runs.
Microtasks run soon, which is useful. But if each microtask keeps adding another one, tasks never get a turn. Timers wait, and the page can't redraw. A later lesson covers promises, timers, and microtasks in depth. For now, keep one rule: first the code running now, then all microtasks, then one task.
How the event loop connects them
INTERACTIVEThe event loop is a routine the host repeats over and over. In a browser, one turn of the loop goes like this:
- Run one task until the call stack is empty.
- Run every callback in the microtask queue, until it is empty.
- Redraw the page, if the browser is ready to.
- Take the next task, and start again.
This short program shows all the parts working together:
console.log("A");setTimeout(() => console.log("B"), 0);Promise.resolve().then(() => console.log("C"));console.log("D");It prints A, D, C, B. Lines 1 and 4 run straight away. Line 2 asks the host for a timer, whose callback will wait in the task queue. Line 3 puts a callback in the microtask queue.
A and D print while the script runs. Then the microtask prints C. Only then does the timer's task print B.In the playground, press Step model to move the program through the map one step at a time. Then press Run in this browser. It runs the same program for real, so you can check the model against your browser.
console.log("A");setTimeout(() => console.log("B"), 0);Promise.resolve().then(() => console.log("C"));console.log("D");- script
empty
empty
empty
- A
press Run to compare
The script starts on the call stack. console.log("A") runs straight away and prints A.
One thread, many helpers
A thread is one line of work that a computer runs step by step. People say "JavaScript is single-threaded". This means your page runs one piece of JavaScript at a time, on one call stack. Two of your functions never run at the same moment on that stack.
That is why a long piece of code freezes the page. While it runs, clicks, timers, and redraws all have to wait.
The host, though, has many helpers that work at the same time as your code:
- A browser has helpers for timers, the network, drawing the page, and events from the mouse and keyboard.
- Node.js uses the computer's operating system and a library called libuv. Some file and encryption work runs on extra libuv threads.
These helpers never run your JavaScript functions. When their work is done, they put a callback in a queue. Later, the engine runs that callback.
A Web Worker runs JavaScript on its own thread, with its own call stack and event loop. It can run at the same time as the page. The worker and the page don't share a call stack. They talk by sending each other messages.
Where you'll use this
PRACTICALThe runtime map helps whenever code seems to run in the wrong order. Ask the matching question:
- A log appears late? Ask which queue it waited in.
- Code works in the browser but fails in Node.js? Ask whether it used a Web API.
- A button stops responding? Ask what kept the call stack busy.
function slowCount() { for (let i = 0; i < 1_000_000; i++) { // The call stack stays busy. Clicks and redraws wait. }} function chunkedCount(total) { let i = 0; function slice() { const end = Math.min(i + 10000, total); while (i < end) i++; if (i < total) setTimeout(slice, 0); } slice();}The first function, slowCount, counts to a million in one go. Like the freeze demo in Engines, runtimes & hosts, it keeps the call stack busy the whole time.
The second function, chunkedCount, splits the work into small pieces called chunks. The inner slice function counts 10,000 more, then uses setTimeout to run the next chunk as a new task. Between chunks, the call stack is empty, so the browser can handle clicks and redraw the page.
let completed = 0;function doSlice() { completed += 2; console.log(completed); if (completed < 4) setTimeout(doSlice, 0);}doSlice();The first call to doSlice adds 2 and prints 2. Because completed is still below 4, it schedules itself again with setTimeout. The second run, as a later task, prints 4 and stops. Chunks keep the page responsive. They don't make the total work smaller, and very tiny chunks add extra waiting between them.
Common misconceptions
- "A zero timer runs right now." Zero only means "don't add a delay". The callback still waits as a later task.
- "Microtasks can run in the middle of a function." They wait until the call stack is empty.
- "Promises use extra threads." A
.thencallback runs as a microtask, on the same thread as the rest of your JavaScript. - "The web page is part of JavaScript." The page, through
document, is a Web API from the browser. - "The heap drawing shows real memory." It is a simple model that explains references.
The table below pairs each wrong idea with what really happens. Read each row from left to right.
| What people think | What really happens |
|---|---|
setTimeout(fn, 0) runs fn right away. | It queues a task. The task runs after the current code and all microtasks. |
A .then callback can interrupt the function that is running. | It waits in the microtask queue until the call stack is empty. |
| Every name you can use is part of JavaScript. | Some come from the host, such as document, fetch, fs, and process. |
| Code that waits for something runs at the same time as other JavaScript. | Callbacks from queues still run one at a time, on one call stack. |
Practice exercises
5 EXERCISESType the words in the order they print. Put commas or spaces between them.
console.log("start");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
queueMicrotask(() => console.log("microtask"));
console.log("end");The script prints start. It starts a timer, queues two microtasks, then prints end. The microtasks print promise and then microtask. The timer's task prints timer last.
On paper, make two columns: engine and host. Write each item from the starter text in the column where it belongs.
Engine: 2 + 3, call stack, object heap
Host: setTimeout, document.querySelector, fs.readFile2 + 3, the call stack, and the heap belong to the engine. Timers, the web page through document, and Node.js file access through fs come from the host.
Which word prints last, and why?
console.log("before");
setTimeout(() => console.log("timer"), 0);
console.log("after");before and after print while the script runs. The timer callback waits in the task queue, so timer prints last.
Rewrite slowCount so it doesn't keep the call stack busy the whole time. Count in small pieces instead, like the demo above.
function slowCount() {
for (let i = 0; i < 1_000_000; i++) {
// The call stack stays busy. Clicks and redraws wait.
}
}
function chunkedCount(total) {
let i = 0;
function slice() {
const end = Math.min(i + 10000, total);
while (i < end) i++;
if (i < total) setTimeout(slice, 0);
}
slice();
}function chunkedCount(total) {
let i = 0;
function slice() {
const end = Math.min(i + 10000, total);
while (i < end) i++;
if (i < total) setTimeout(slice, 0);
}
slice();
}The new version counts in small pieces. Between pieces, the call stack is empty, so the browser can handle clicks and redraw the page.
Draw the boxes and arrows on paper, then compare your drawing with the solution.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");script task → call stack
console.log("A") → console
setTimeout(...) → host timer → task queue
Promise.then(...) → microtask queue
console.log("D") → console
microtasks run → C
timer task runs → BA good drawing shows A and D printing while the script runs, C printing from the microtask, and B printing from the timer's task.
Check your understanding
7 QUESTIONSQuestion 1 of 7Which part of the engine remembers the function calls that are running right now?
Choose an answer to see the explanation.
Question 2 of 7What does this timer program print?
Read the code, then predictconsole.log("A"); setTimeout(() => console.log("B"), 0); console.log("C");Choose an answer to see the explanation.
Question 3 of 7What does this promise program print?
Read the code, then predictconsole.log("A"); Promise.resolve().then(() => console.log("B")); console.log("C");Choose an answer to see the explanation.
Question 4 of 7After a script finishes, what does the event loop run before the next timer or click task?
Choose an answer to see the explanation.
Question 5 of 7Which of these comes from Node.js, not from the JavaScript language?
Choose an answer to see the explanation.
Question 6 of 7Why can
setTimeout(fn, 0)still run later than a promise.thencallback?Choose an answer to see the explanation.
Question 7 of 7What does this object program print?
Read the code, then predictconst chef = { tickets: 1 }; const alias = chef; alias.tickets = 2; console.log(chef.tickets);Choose an answer to see the explanation.
Key takeaways
- The engine runs the JavaScript language. It keeps the call stack, and it keeps objects in the heap.
- The host adds extras: the web page, timers, and
fetchin a browser, or files andprocessin Node.js. - A plain function call runs right away. Timer and click callbacks wait as tasks.
.thencallbacks wait as microtasks. - The event loop waits for an empty call stack, runs every microtask, then starts the next task.
- Your JavaScript runs one piece at a time, but the host has helpers that work at the same time.
A JavaScript runtime is an engine plus a host. The engine runs one piece of JavaScript at a time and keeps objects in memory. The host adds extras, keeps callbacks waiting in queues, and uses the event loop to run each one when the call stack is free.
Up next: Execution contexts.