Engines, runtimes & hosts
Learn how the JavaScript engine that runs the language differs from the browser, server runtime, or host APIs around it.
- 01The engine's jobName the V8, SpiderMonkey, and JavaScriptCore engines, and say what an engine does.
- 02The host's jobTell the JavaScript language apart from the extra tools that browsers, Node.js, Deno, and Bun add.
- 03One main threadSee why long synchronous work freezes a page, and how small slices keep it working.
The engine, the host, and the runtime
People say "JavaScript runs in Chrome." They also say "JavaScript runs in Node.js" and "JavaScript has document.querySelector." Each sentence talks about a different part. This lesson gives each part its name.
There are three words to learn:
- A JavaScript engine is a program that reads your JavaScript code and runs it. It knows the rules of the language: variables, functions, arrays,
Promise, and so on. - The host is the bigger program that the engine sits inside, such as a browser tab or Node.js. The host adds extra tools that reach the outside world, such as the web page or the files on your computer. These extra tools are called host APIs. An API is a set of names your code can use to ask another program to do something.
- A runtime is a program you install to run JavaScript. It comes with an engine and the extra tools. Node.js, Deno, and Bun are runtimes. They run JavaScript outside a browser, for example on a server. A server is a computer that sends web pages and data to other computers.
document for the page.The engine runs JavaScript. The host gives JavaScript a place to run and extra tools to reach the outside world.
The next lesson, Manuals & specifications, shows where to look up the exact rules of the language and which browsers support a feature. For this lesson, you only need your browser.
V8, SpiderMonkey & JavaScriptCore
MAP ITThere is more than one JavaScript engine. Every engine does the same jobs:
- It reads your code.
- It checks that the code follows the grammar of JavaScript. This grammar is called syntax.
- It creates the values and functions that your code describes.
- It runs your statements and gives back the results.
Different companies build different engines. They all follow the same rule book for JavaScript, the ECMAScript standard from the previous lesson.
The table lists the three engines you'll hear about most. Read each row from left to right: the engine's name, the programs that use it, and who builds it.
| Engine | Which programs use it? | Who builds it? |
|---|---|---|
| V8 | Chrome, Edge, Opera, Node.js, and Deno use it. | Google builds V8. |
| SpiderMonkey | Firefox uses it. | Mozilla builds SpiderMonkey. |
| JavaScriptCore | Safari and Bun use it. | Apple builds JavaScriptCore. |
An engine's name is not the name of a different language. All three engines run the same JavaScript. Most everyday code works the same in all of them, because they follow the same standard.
A browser is more than its engine. It also draws the page, handles clicks, and much more. So "Chrome" and "V8" are not two words for the same thing.
const price = 3;
const quantity = 2;
console.log(price * quantity);This code uses only numbers and *, the multiply sign. Both are part of the JavaScript language. So every modern engine can run it and print 6.
This does not mean every host has the same extra tools for pages, files, or the network. It only shows that the rules of the language come with every engine.
const browser = "Firefox";
const engine = "SpiderMonkey";
console.log(browser + " uses " + engine);Line 1 stores the name Firefox. Line 2 stores the name of its engine, SpiderMonkey. Line 3 joins them into one sentence and prints Firefox uses SpiderMonkey. The two names are typed by hand. The code does not check which browser is running it.
const runtime = "Node.js";
const engine = "V8";
console.log(runtime + " uses " + engine);This is the same three-line pattern for a runtime. Line 1 stores Node.js, line 2 stores V8, and line 3 prints Node.js uses V8. Again, the names are typed by hand. The code does not check which program is running it.
A car engine does not decide whether the car has two seats, a big trunk, or a roof rack. It only gives power. The rest of the car decides what the car can do. One engine can go into a van and into a truck. The engine is the same, but each car can do different things.
- In real life: A car engine makes the car move
- In JavaScript: A JavaScript engine makes your code run
- In real life: The body can be a van, a sports car, or a truck
- In JavaScript: The host can be Chrome, Safari, Node.js, Deno, or Bun
- In real life: The same engine can go into several car models
- In JavaScript: V8 sits inside Chrome and inside Node.js
document. Node.js adds server tools such as process.Engine names come up in a few places. You see them in release notes, which list the changes in a new version. They help when code works in one browser but not in another. And you hear them when someone says a runtime is "built on V8."
Engine names do not mean you write three versions of your code. For beginner code, almost everything works the same in every engine.
Browsers vs Node.js, Deno & Bun
INTERACTIVEEach host adds its own extra tools:
- A browser adds tools for the web page:
window,document,localStorage,alert, and many more. - A runtime such as Node.js adds tools for servers. Your code can read files, read the words typed after the command that started the program, and read settings stored outside the program.
These are host APIs. The host gives you these names. The JavaScript language does not.
const hasPage = typeof document !== "undefined";
console.log(hasPage ? "page available" : "no page");typeof gives the type of a value as text, such as "object". Most code fails when it uses a name that does not exist. typeof does not fail. It gives "undefined" instead.
So line 1 is safe even when there is no document. In a browser page, this code prints page available. In Node.js, it prints no page. Check like this before you use a host API in code that can run in both places.
The clearest test is to ask the host which global names exist. A global name is one your code can use anywhere, without creating it first. Run this experiment in your browser and look at three results:
windowanddocumentexist here, because this is a browser.processusually does not exist here. It belongs to Node.js.globalThisexists in every modern host. It is the standard name for the object that holds all the global names.
const info = { window: typeof window, document: typeof document, process: typeof process, globalThis: typeof globalThis, userAgent: navigator.userAgent,};console.table(info);- typeof window
- not checked yet
- typeof document
- not checked yet
- typeof process
- not checked yet
- typeof globalThis
- not checked yet
Engine guess: Click Run check to ask this browser.
User agent: waiting for the browser
This code ran in your browser. It found window=not checked yet, document=not checked yet, process=not checked yet, globalThis=not checked yet.
Now pick one name and check it. The answer comes from the page you are reading, so another browser or Node.js may give a different answer. The in operator checks whether an object has a property with a given name. Here it checks globalThis.
const name = "document";console.log(name in globalThis);checking…
Checking whether this host has document…
The engine guess reads navigator.userAgent. This is a line of text the browser gives about itself. It is only a clue, not proof. Browsers can shorten this text, stop updating it, or pretend to be another browser.
So don't trust the exact text. Remember the main idea instead: the same language runs inside different hosts.
You can also step through the checks one at a time. This replay asks whether the current host has document and whether it has Node's process. Compare the answers with the live values above.
This replays two real checks in the host you are using now. It is not a view inside the engine.
script
const hasProcess = "process" in globalThis;console.log(hasDocument + " " + hasProcess);Node.js, Deno, and Bun are runtimes. You install one on your computer to run JavaScript outside a browser. Node.js and Deno use the V8 engine. Bun uses JavaScriptCore. Each one adds its own command-line tools and host APIs.
Language features vs host APIs
SORT ITWhen someone says code "works in JavaScript," ask a more exact question: is this part of the language, or did the host add it?
- Language features come with every engine, so they work in every host.
- Host APIs depend on where the code runs.
const cart = ["tea"];
const labels = cart.map((item) => item.toUpperCase());
console.log(labels[0]);Line 1 makes a cart array with one item, "tea". Line 2 uses map, an array method from the language. It makes a new array with each item in capital letters. Line 3 prints TEA.
This code works in a browser and in Node.js. A page tool such as document does not.
const savedCart = '{"item":"tea","quantity":2}';
const cart = JSON.parse(savedCart);
console.log(cart.quantity);This is another language feature. Line 1 holds text that could come from saved data or from a server. On line 2, JSON.parse turns that text into a JavaScript object. So line 3 prints 2. The code needs only a string. It does not need a browser page.
Array.prototype.mapJSON.parseMath.maxPromisedocument.querySelectorlocalStoragefetchalertprocess.argvrequire('fs')setTimeoutconsole.log
Sort each name by who gives it to you. Read the explanation after each card. Some answers are close on purpose.
Use this table when a name works in one place but not in another. Find what you see in the first column. Then ask the question in the second column.
| What do you see? | What should you ask? | Examples |
|---|---|---|
Syntax, or a built-in object such as Array or JSON. | Does this engine support this language feature? | Array.prototype.map, JSON.parse, Promise |
| A tool for the page, storage, pop-ups, or the network. | Does this host give me that API? | document, localStorage, alert, fetch |
| A tool for files, command-line words, or packages. | Which runtime is this code written for? | process.argv, require('fs') |
| A name that works in many places. | Do several hosts each give me this API? | console, setTimeout, and often fetch |
Knowing the difference saves you from a common mix-up:
Array.prototype.mapworks in Node.js and in a browser, because arrays are part of the language.document.querySelectorfinds an element on the page. It works in a browser, because the browser gives you a page. In plain Node.js there is no page, so it fails.fetchasks a server for data over the network. It is a host API, and browsers and most runtimes give it to you. When its details matter, check the runtime's documentation.
map is part of the JavaScript language, so it works in every host. document is an extra from the browser. Node.js has no page, so it gives an error.function showGreeting(page, user) {
if (!page) return "No page to update";
page.textContent = "Hi, " + user;
return page.textContent;
}
console.log(showGreeting(null, "Asha"));showGreeting takes a page element and a user's name. Line 2 checks the element. If there is none, the function returns "No page to update" and stops.
Line 6 passes null, which means "nothing here." So the code prints No page to update. In a browser, you could pass an element from document.querySelector instead. The page work sits in one small spot, so the function works in any host without an error.
The next replay steps through a script that calls one function. Pick a host name for line 1. Then predict what line 7 prints.
Predict the sentence that line 7 prints. Then step through how the script calls one function and comes back.
script
function describeHost(place) { const kitchen = place + " host"; return "JavaScript running in a " + kitchen;}const summary = describeHost(host);console.log(summary);One main thread at a time
REAL FREEZEA thread is one line of work that a computer does, one step after another. The browser runs ordinary page code on its main thread. The main thread also updates what you see on the page. It does one job at a time.
When code calls a function, the code around it pauses. The function runs and returns. Then the next statement runs. Code that runs straight through like this, one statement after another, is called synchronous code.
Picture one cashier in a small shop. A customer with a huge order makes everyone else wait. If the cashier rings up that order in small parts and serves quick customers in between, the line keeps moving.
- In real life: One cashier serves one customer at a time
- In JavaScript: The main thread runs one JavaScript task at a time
- In real life: A huge order makes everyone behind it wait
- In JavaScript: A long loop makes clicks and page updates wait
- In real life: The cashier rings up the huge order in parts and serves others in between
- In JavaScript: Timers split the work into slices, and the host gets a turn between slices
Try the demo. The counter goes up each time the browser redraws the page. The first button runs a busy loop for about 1.5 seconds. While the loop runs, the counter stops and clicks do nothing.
The second button does about the same amount of work in small slices. It uses setTimeout, which asks the host to run a function a little later. Between slices, the browser gets a chance to redraw the page.
let frame = 0;function tick() { frame = frame + 1; requestAnimationFrame(tick);} function blockFor(milliseconds) { const end = performance.now() + milliseconds; while (performance.now() < end) { // Busy work keeps the main thread occupied. }} function chunkedWork(milliseconds) { const end = performance.now() + milliseconds; function slice() { const sliceEnd = Math.min(performance.now() + 25, end); while (performance.now() < sliceEnd) {} if (performance.now() < end) setTimeout(slice, 0); } slice();}Try clicking another button while the busy loop runs. The click waits until the loop ends.
The counter goes up each time the page redraws. Try the busy-loop button, then the slices button.
The next code splits five units of work into slices of at most two. It does not schedule anything. It only works out how big each slice is. In real code, a timer can plan the next slice, but the browser decides when that slice gets its turn.
const totalUnits = 5;
const sliceSize = 2;
const slices = [];
for (let completed = 0; completed < totalUnits; completed += sliceSize) {
slices.push(Math.min(sliceSize, totalUnits - completed));
}
console.log(slices.join(", "));Line 1 sets five units of work. Line 2 sets a slice size of two. Line 3 makes an empty array, slices. The loop on lines 4 to 6 adds one slice each time round. Math.min picks the smaller of two numbers, so the last slice holds only what is left. The slices are two units, two units, then one. Line 7 prints 2, 2, 1.
console.log("first");
setTimeout(() => console.log("later"), 0);
console.log("second");The output order is first, second, then later. setTimeout asks the host to run a function later. Here the delay is 0, but the function still waits until the current script has finished.
setTimeout does not pause the script. It also does not run the function at the same time as the script.
There is one more way to do a big job. A Web Worker runs JavaScript on a separate thread. It has its own global names, and it can't change this page directly. It sends a message to the page instead.
This table compares the three ways. Read each row from left to right.
| How is the work done? | What does the main thread do? | What does the user notice? |
|---|---|---|
| In one long loop. | It keeps running the loop until all the work is done. | Clicks and page updates wait until the loop ends. |
| In small slices. | It finishes one slice, then a timer plans the next one. | The page can handle clicks and redraw between slices. |
| In a Web Worker. | It stays free, because a separate thread runs the work. | The page stays quick, but it needs a message from the worker to show the result. |
This is not the full lesson on scheduling. Later, The event loop lesson explains tasks and timers in depth. For now, remember one rule: while synchronous code runs, the main thread can't handle clicks or update the page.
Step through this small example of synchronous code: a log, a function call, the function's return, and the logs after it.
Step through the first log, the function call, the return, and the lines after it.
script
function cook(order) { console.log("cooking " + order); return order.toUpperCase();}const result = cook("bun");console.log(result);console.log("script end");Where you’ll use this
Knowing the engine from the host turns confusing errors into useful clues:
- If the console says
document is not defined, you probably ran browser code in Node.js or another runtime. - If it says
process is not defined, you probably ran Node.js code in a browser. - If an older browser does not know a new method, the problem is support for that feature, not a typo.
function getCartTotal(cart) {
return cart.price * cart.quantity;
}
console.log(getCartTotal({ price: 3, quantity: 2 }));Lines 1 to 3 make a function, getCartTotal. Line 2 multiplies the price by the quantity. Line 4 calls the function and prints 6. This math uses only plain values, so it runs in any host. Only the code that writes the total onto a page needs a browser.
const cart = [
{ price: 3, quantity: 2 },
{ price: 2, quantity: 1 },
];
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
console.log(total);A real cart usually has more than one item. Lines 1 to 4 make an array of two items. reduce is an array method that goes through the items one by one and builds a single result.
Line 5 starts from 0 and adds price times quantity for each item: 3 times 2, plus 2 times 1. Line 6 prints 8. This works in any host, because it uses only arrays and numbers.
You will use this idea in everyday work:
- MDN is a free reference website for web developers. When you read it, check whether a page is about a JavaScript language feature or a Web API. A Web API is a host API that browsers give you.
- A package is code someone else wrote that you install and use. Before you pick one, check whether it needs Node.js APIs, browser APIs, or both.
- When a page feels frozen, look for long synchronous work on the main thread.
- When you compare Node.js, Deno, and Bun, ask two questions: "Which engine?" and "Which host APIs?"
Before you blame JavaScript, ask three questions. Does this engine support the language feature? Does this host have the API? Is a long task blocking the main thread? These three questions solve many beginner problems.
Common misconceptions
"V8 is JavaScript."
V8 is one engine that runs JavaScript. JavaScript is the language. An engine is a program that runs it.
"Node.js and the browser are basically the same."
They run the same language, but they give you different host APIs. A browser has a page. Node.js has tools for servers and files.
"If Chrome has it, JavaScript has it."
Chrome gives you JavaScript plus many browser APIs. Some names you use in Chrome, like document, come from the browser, not from the language.
"Single-threaded means slow."
Single-threaded means one task runs at a time on the main thread. Short tasks are fine. Only long tasks that block the thread cause trouble.
"setTimeout makes work run in parallel."
Parallel means two things run at the same moment. A timer only plans work for later. You can use timers to split work into slices, but the slices still run one at a time on the main thread.
Practice: spot the layer
6 EXERCISESOpen the browser console and type typeof window. Then type here what it prints.
In a normal browser page, typeof window prints object, because the browser host gives you window. In a host with no page, such as Node.js, it can print undefined.
processType typeof process in your browser console. If you like, try it again in Node.js once the next lessons have set it up.
In a normal browser page, typeof process is usually undefined. In Node.js, process is an object that the host gives you, so the type is object.
Don't run the code yet. Write the three lines it prints, in order.
console.log("A");
function run() {
console.log("B");
}
run();
console.log("C");Line 1 prints A. Line 5 calls run(), so the function runs and line 3 prints B. Then the script goes on, and line 6 prints C.
Use the engine table and the host experiment. Which engine does your browser most likely use?
Find your browser in the table, then read its engine: V8 for Chrome, Edge, and Opera, SpiderMonkey for Firefox, and JavaScriptCore for Safari. The user agent is only a clue, not proof.
Use the groups from the sorter. process.argv is the list of words typed after the command that started the program. Which group does it belong to?
Array.prototype.map -> language
process.argv -> Node host
document.querySelector -> browser host
setTimeout -> shared host APIArray.prototype.map -> language
process.argv -> Node host
document.querySelector -> browser host
setTimeout -> shared host APIArray.prototype.map is part of the language. process.argv comes from the Node.js host. document.querySelector comes from the browser host. setTimeout is a timer that both browsers and Node.js give you.
The same cart code runs twice: first on the server, to build the page, and later in the browser. Work out the total in both places. Update #cart-total only when there is a page and that element exists.
function showCartTotal(cart) {
const total = cart.price * cart.quantity;
document.querySelector("#cart-total").textContent = total;
return total;
}
console.log(showCartTotal({ price: 3, quantity: 2 }));function showCartTotal(cart) {
const total = cart.price * cart.quantity;
if (typeof document !== "undefined") {
const output = document.querySelector("#cart-total");
if (output) output.textContent = total;
}
return total;
}
console.log(showCartTotal({ price: 3, quantity: 2 }));The total is plain JavaScript, so any host can work it out. The typeof document check lets the page update run only in a browser. The if (output) check skips the update when the page has no #cart-total element. The program prints 6 in both hosts.
Quiz: engines, runtimes & hosts
7 QUESTIONSCheck what you learned. Each answer explains why it is right or wrong.
Question 1 of 7What does a JavaScript engine do?
Choose an answer to see the explanation.
Question 2 of 7Which engine does Firefox use?
Choose an answer to see the explanation.
Question 3 of 7What does this line print?
Read the code, then predictconsole.log(typeof globalThis);Choose an answer to see the explanation.
Question 4 of 7Which one is a browser host API, not a language feature?
Choose an answer to see the explanation.
Question 5 of 7What does this print in a normal browser page?
Read the code, then predictconsole.log(typeof process);Choose an answer to see the explanation.
Question 6 of 7Why does a 1.5-second busy loop freeze the moving counter?
Choose an answer to see the explanation.
Question 7 of 7In what order do these logs print?
Read the code, then predictconsole.log("start"); function label() { console.log("inside"); } label(); console.log("end");Choose an answer to see the explanation.
Key takeaways
- V8, SpiderMonkey, and JavaScriptCore are engines: programs that run the JavaScript language.
- Browsers, Node.js, Deno, and Bun are hosts. Each one holds an engine and adds its own APIs. Node.js, Deno, and Bun are also called runtimes.
- Language features such as arrays,
JSON,Math, andPromisework in every host. Host APIs such asdocument,process, timers, and storage depend on the host. - Ordinary page code runs one task at a time on the main thread. Long synchronous work freezes the page.
- When code fails, ask three questions. Is this the wrong host? Is the feature not supported? Is a long task blocking the main thread?
Remember the one-liner.
The engine runs the language. The host gives it the world around it.
Coming next: Manuals & specifications.