Beyond window: Understanding global and globalThis in JavaScript

When you wrote your first line of Node.js code, you probably typed console.log("Hello World") and it just worked. No import. No setup. Nobody told you where console came from, and at the time, it did not matter.
But if you have ever stopped to wonder why console, setTimeout, and process are just... there, ready to use before your code does anything, the answer is global. It is the object Node.js quietly builds before your code even starts running, and it holds everything the runtime gives you for free.
Then in 2020, JavaScript introduced globalThis, which sounds like a small thing but solved a problem that had been quietly annoying developers for years.
What is Node.js, Actually?
JavaScript was built for browsers. Chrome, Firefox, Safari, all of them come with a JavaScript engine and a set of built-in tools like document, window, and fetch that let your code interact with a webpage.
Node.js had a different idea. It took V8, the same engine Chrome uses to run JavaScript, and stripped out everything browser-specific. The result was a JavaScript runtime that could run anywhere: on a server, on your machine, inside a build tool, wherever.
The language is the same. The environment is different. And just like browsers have an object that holds everything your code needs without importing it, Node.js has its own version of that.
In the browser, that object is window.
In Node.js, it is global.
global: The Object That Holds Everything
When Node.js starts up, it creates the global object and populates it with every built-in tool your code might need. When you type console.log(), Node.js is actually looking up global.console.log() behind the scenes.
// What you write:
console.log("I am global");
setTimeout(() => {}, 1000);
// What Node.js actually does:
global.console.log("I am global");
global.setTimeout(() => {}, 1000);
Node.js lets you skip the global. prefix, so it feels like these things exist on their own. But they are just properties on that one shared object.
console.log(global.console === console); // true
console.log(global.setTimeout === setTimeout); // true
Here is a look at what lives on global by default:
| Category | What you get |
|---|---|
| Output | console |
| Timers | setTimeout, setInterval, setImmediate, clearTimeout, clearInterval |
| Module system | require, module, exports, __filename, __dirname |
| Runtime info | process |
| Data structures | Buffer |
| Async primitives | Promise |
| Universal JS | Math, JSON, Date, Error |
Why this at the Top Level is Not global
Here is something that catches a lot of people off guard. If you open a Node.js file and log this at the top level, you do not get global.
console.log(this); // {}
console.log(this === global); // false
The reason is that Node.js does not actually run your file directly. Before execution, it wraps your entire file in a function:
// Node.js silently does this before running your code:
(function(exports, require, module, __filename, __dirname) {
// everything you write ends up in here
});
So your code is never truly at the top level. It is inside a function, and inside that function, this points to module.exports, not global. module.exports starts as an empty object, which is why you see {}.
console.log(this); // {}
console.log(this === global); // false
console.log(this === module.exports); // true
If you call a regular function (not a method, not in strict mode), this inside it does resolve to global:
function test() {
console.log(this === global); // true
}
test();
Here is the full picture:
| Where the code runs | What this becomes |
|---|---|
| Browser top level | window |
| Node.js top level | module.exports (empty {}) |
| Regular function call (non-strict) | global |
| Strict mode function | undefined |
| Method call | the object the method belongs to |
| Any environment | globalThis |
The Problem globalThis Was Built to Solve
For a long time, Node.js developers used global and browser developers used window. That was fine until someone needed to write code that ran in both.
Say you build a utility library for formatting dates or handling errors and publish it on npm. Browser apps use it. Node.js apps use it. The moment you try to reference the global object directly, you are stuck:
// Crashes in Node.js
console.log(window.Math);
// ReferenceError: window is not defined
// Crashes in a browser
console.log(global.Math);
// ReferenceError: global is not defined
Neither name works in both places. So developers wrote workarounds like this:
const globalObject =
typeof window !== "undefined" ? window :
typeof global !== "undefined" ? global :
typeof self !== "undefined" ? self :
null;
It worked, but it was fragile and ugly. Every shared library had its own version of this block.
ES2020 replaced all of that with one thing.
globalThis: One Name That Works Everywhere
globalThis is a standard, universal way to access the global object regardless of where the code is running.
// In Node.js:
console.log(globalThis === global); // true
// In a browser:
console.log(globalThis === window); // true
// In a Web Worker:
console.log(globalThis === self); // true
That messy workaround from above becomes:
const globalObject = globalThis;
globalThis did not replace global in Node.js. global is still valid and still works. The difference is portability:
| Environment | global works |
globalThis works |
|---|---|---|
| Node.js | Yes | Yes |
| Browser | No | Yes |
| Web Workers | No | Yes |
| Deno | No | Yes |
If you are writing code that will only ever run in Node.js, global is fine. If there is any chance your code ends up in a browser or gets shared as a library, use globalThis.
// Both work in Node.js and point to the same object
console.log(global === globalThis); // true
console.log(global.Math.PI); // 3.141592653589793
console.log(globalThis.Math.PI); // 3.141592653589793
console.log(globalThis.setTimeout === setTimeout); // true
Do Not Put Your Own Variables on global
Node.js will let you attach your own values to global. It will not throw an error. But it is a pattern that tends to create problems you only discover months later.
Here is a common beginner mistake. You have a username that needs to be accessible across multiple files, and attaching it to global seems like a clean shortcut:
// app.js
global.username = "User";
console.log("App started");
// greet.js
function greet() {
console.log("Hello, " + username); // no import, just works
}
greet(); // "Hello, User"
This works right up until you have ten files, three months of code, and a bug where the username is printing wrong. You open greet.js looking for where username comes from, and there is nothing. No import. No declaration. You now have to trace through every file in the project to find where it was set.
Global variables have no trail. They appear from nowhere and make debugging slow and painful, especially when someone new joins the project.
The fix is straightforward:
// user.js
const username = "User";
module.exports = username;
// greet.js
const username = require("./user");
function greet() {
console.log("Hello, " + username);
}
greet(); // "Hello, User"
Now anyone reading greet.js knows immediately where username comes from. No searching required. As the codebase grows, that clarity pays off every single time.
Conclusion
JavaScript was built for the browser, and Node.js pulled it out of that environment entirely. With that move came the need for a global object of its own, and that is global. Every built-in you use in Node.js without importing, console, setTimeout, process, Buffer, lives there.
The this behavior at the top level of a file is a side effect of how Node.js wraps your code in a function before running it. Once you know that, the {} output makes sense.
globalThis came along in ES2020 to give developers one consistent name for the global object across every JavaScript environment. In Node.js specifically, global and globalThis point to the same place. Which one you use depends on whether your code is staying in Node.js or needs to travel.
Either way, now you know where console.log actually comes from.





