βοΈ JavaScript Execution Environment
The same JavaScript file behaves differently depending on where it runs. This lesson maps the two homes JavaScript lives in β the browser and Node.js β and then goes under the hood to the engine, the call stack, and the event loop that let a single-threaded language stay responsive.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Define a JavaScript execution environment and name the capabilities each one provides
- Contrast the browser environment (DOM,
window, Web APIs) with Node.js (file system,global, modules) - Explain how the engine, call stack, and memory heap execute your code
- Trace how the event loop and callback/microtask queues handle asynchronous work
- Write environment-aware code that detects where it is running
Estimated Time: 30β40 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Predict the output order of a mixed synchronous / async snippet, then verify it.
In This Lesson
What Is an Execution Environment?
An execution environment is the "world" your JavaScript lives in. The core language β variables, functions, loops, objects β is defined by ECMAScript and is identical everywhere. But the language on its own can't open a file, draw on the screen, or make a network request. Those powers come from the environment wrapped around the engine.
π‘ Same skills, different fields. A tennis player uses the same strokes on clay, grass, and hard courts, but adapts to each surface. JavaScript keeps its syntax everywhere, but the browser and Node.js give it very different surfaces to play on.
The two environments you'll meet constantly are the browser and Node.js. Each hands JavaScript a different toolbox:
The Browser Environment
The browser is JavaScript's original home. Here the global object is window, and the browser exposes a rich set of Web APIs for interacting with the page and the device.
windowβ the global object representing the browser tab; global variables and functions hang off it.- DOM (Document Object Model) β a live, tree-shaped representation of the HTML that JavaScript can read and change.
- Web APIs β
fetchfor network requests,localStoragefor storage, geolocation, canvas, audio, notifications, and more.
// In the browser, the global object is window
window.location.href; // the current URL
window.localStorage; // key/value storage API
// Feature-detect before using an API
if ('localStorage' in window) {
localStorage.setItem('theme', 'dark');
} else {
console.log('This browser has no localStorage');
}
The DOM as a tree
The DOM turns your HTML into a tree of nodes. JavaScript walks and edits that tree to build interactive pages.
// Read and change the DOM
const heading = document.querySelector('h1');
heading.textContent = 'Updated by JavaScript';
// Create and insert a new element
const note = document.createElement('p');
note.textContent = 'Added at runtime';
document.body.appendChild(note);
// React to user input
document.querySelector('button')?.addEventListener('click', () => {
console.log('Button clicked!');
});
π The DOM as a family tree
Think of document as the ancestor and every element as a descendant. We navigate by relationships β parent, child, sibling β and "editing the DOM" is like adding, renaming, or pruning branches of that family tree.
The Node.js Environment
Node.js (Ryan Dahl, 2009) took Chrome's V8 engine out of the browser and paired it with system-level APIs, so JavaScript could run on servers and command-line tools. There is no window and no DOM here β instead you get access to the machine.
globalβ the global object, in place ofwindow.fsβ read and write files.http/httpsβ create web servers and clients.processβ details about the running process (version, platform, environment variables).- npm β a registry of millions of installable packages.
A minimal web server in Node.js:
// server.js β a bare-bones HTTP server
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
π‘ Tour guide vs. general contractor. Browser JavaScript is like a tour guide who knows one museum (the page) inside out and helps visitors interact with it. Node.js JavaScript is like a general contractor who can build whole structures and manage utilities. Same language, very different jobs.
Browser vs. Node.js
The language is the same; the surroundings differ. This table captures the differences you'll hit most often.
| Feature | Browser | Node.js |
|---|---|---|
| Global object | window | global (or globalThis) |
| DOM API | Available | Not available |
| File system | Very limited (File API) | Full access (fs module) |
| Network requests | fetch | fetch, http/https |
| Module system | ES Modules (import/export) | CommonJS (require) & ES Modules |
| Security model | Sandboxed, many restrictions | Full system access (within user permissions) |
globalThis is worth remembering: it's the standardized name for the global object that works in every environment, so you no longer have to guess between window and global. (And note that fetch is now built into modern Node.js too, so that gap has narrowed.)
Writing environment-aware code
Some code runs in both places β this is called isomorphic or universal JavaScript, and frameworks like Next.js and Nuxt rely on it. You branch on what's available:
async function loadProduct(id) {
if (typeof window === 'undefined') {
// Server (Node.js): talk to the database directly
const db = require('./database');
return db.query('SELECT * FROM products WHERE id = ?', [id]);
} else {
// Client (browser): call our own API
const response = await fetch(`/api/products/${id}`);
return response.json();
}
}
β οΈ A common beginner error
Calling document.getElementById(...) in Node.js throws ReferenceError: document is not defined, and calling require('fs') in the browser fails too. When code has to run in both places, guard environment-specific calls behind a check like the one above.
Inside the Runtime
Whichever environment you're in, a JavaScript engine does the actual work of parsing and running your code. The famous ones are V8 (Chrome, Node.js), SpiderMonkey (Firefox), and JavaScriptCore (Safari).
The call stack
JavaScript is single-threaded: it does exactly one thing at a time, tracked by the call stack. Calling a function pushes a frame on top; returning pops it off. The last function pushed is the first to finish β last in, first out, like a stack of plates.
function first() {
console.log('first: start');
second();
console.log('first: end');
}
function second() {
console.log('second: start');
third();
console.log('second: end');
}
function third() {
console.log('third');
}
first();
// first: start
// second: start
// third
// second: end
// first: end
The Event Loop
If JavaScript can only do one thing at a time, how does it fetch data or wait for a timer without freezing? The answer is the event loop. Slow operations are handed off to the environment's APIs; when they finish, their callbacks wait in a queue. The event loop moves a waiting callback onto the call stack only once the stack is empty.
console.log('First');
setTimeout(() => {
console.log('Second β after the delay');
}, 2000);
console.log('Third');
// Output:
// First
// Third
// Second β after the delay
Microtasks jump the line
There are actually two queues. Promise callbacks go on the microtask queue, and the event loop empties all microtasks before touching the ordinary callback (macrotask) queue used by timers. That's why promises resolve ahead of a setTimeout(..., 0):
console.log('Start');
setTimeout(() => console.log('Timeout'), 0); // macrotask
Promise.resolve().then(() => console.log('Promise')); // microtask
console.log('End');
// Output:
// Start
// End
// Promise β microtasks run before timers
// Timeout
β Why this matters
Without the event loop, one slow network call would freeze the entire page β no scrolling, no clicks. With it, JavaScript delegates the wait, keeps the interface responsive, and runs your callback the moment the result is ready. This model is the beating heart of every interactive web app.
Hands-on Exercise
ποΈ Predict the Event Loop
Objective: Reason about ordering across synchronous code, microtasks, and macrotasks β then confirm in a real console.
The code:
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve()
.then(() => console.log('3'))
.then(() => console.log('4'));
console.log('5');
Your task:
- Write down the order you expect the numbers to print, before running anything.
- Open your browser console (or a Node.js REPL), paste the code, and run it.
- Explain any surprise using the two-queue model from this lesson.
π‘ Hint
First, all the synchronous lines run top to bottom. Then the microtask queue (the .then callbacks) drains completely. Only after that does the event loop reach the setTimeout macrotask.
β Solution
The output is:
1
5
3
4
2
Why: 1 and 5 are synchronous, so they print first in order. The two .then callbacks are microtasks, so 3 then 4 print before any timer. The setTimeout callback is a macrotask and runs last, printing 2 β even though its delay was 0.
π― Quick Quiz
Question 1: Which capability exists in the browser environment but not in Node.js?
Question 2: JavaScript is single-threaded. What lets it handle asynchronous work without blocking?
Question 3: In the event loop, when do promise callbacks (microtasks) run relative to setTimeout callbacks?
Summary & Quiz
π Key Takeaways
- An execution environment wraps the engine with the APIs your code actually needs β the language alone can't touch files, screens, or networks.
- The browser gives you
window, the DOM, and Web APIs; Node.js gives youglobal, the file system, and servers. - The engine (V8, SpiderMonkey, JavaScriptCore) runs code with a memory heap and a single call stack.
- The event loop lets single-threaded JavaScript stay responsive by queueing callbacks and running them when the stack is clear.
- Microtasks (promises) run before macrotasks (timers) β a detail that explains a lot of "surprising" output order.
π Further Reading
- MDN β The event loop
- Node.js β The event loop, timers, and process.nextTick
- MDN β Web API reference
π What's Next?
You now know where JavaScript runs and how it schedules work. Next you'll get hands-on with the single most useful tool for exploring all of this live β the browser console.
π Great progress!
The runtime is no longer a black box. Time to open the console and start poking at it.