A JavaScript global variable is a variable that every file and every function of a program can read and change. In modern code, the portable way to create one is a property of globalThis, the standard name of the global object in browsers, Node.js and web workers since ES2020. Whether a plain var, let or const at the top of a file becomes global depends on how the file runs. In a classic browser script the variable becomes global, whereas in an ES module or a Node.js CommonJS file it stays in the file.
We use a global variable for a value that the whole program shares, such as a request counter or a setting that a server-rendered page passes to browser code. For example, a shop page puts appName on window, and every script on the page reads it.
The following example shows each way to create or avoid a global, with the result of each line as a comment.
// 1. A top-level const in an ES module stays in the module
const appName = "Shop";
const inGlobal = "appName" in globalThis; // inGlobal = false
// 2. A real global variable with globalThis (ES2020)
globalThis.requestCount = 0;
globalThis.requestCount++;
const count = requestCount; // count = 1
// 3. Implicit global: strict mode and modules reject it
// total = 5; // ReferenceError: total is not defined
// 4. Shadowing: a local name hides the global one
{
const requestCount = 100;
const local = requestCount; // local = 100
}
// 5. Better: a frozen config object in its own module
const config = Object.freeze({ appName: "Shop", apiBaseUrl: "/api" });
// config.appName = "Blog"; // error TS2540, TypeError at runtime
// 6. Config values from environment variables (Node.js)
const apiBaseUrl = process.env.API_BASE_URL ?? "/api"; // apiBaseUrl = "/api"
// 7. TypeScript: declare the global once, in a .d.ts file
// declare global { var requestCount: number; }
Notice that a top-level const in an ES module never reaches globalThis, so a real global needs an explicit globalThis assignment.
Next, we create a global with globalThis and compare how var differs from let and const at the top level of scripts and modules. After that, we see why strict mode rejects implicit globals and type globals in TypeScript with declare global. We also look at the modules and environment variables that replace globals in real projects, with an app configuration (such as appName and featureFlags) and a request counter as the running example.
1. What Is Global Scope in JavaScript?
A scope is the part of a program where a name is visible. JavaScript nests four kinds of scope, namely block scope (the code between braces), function scope, module scope and the global scope at the top. When code uses a name, the engine searches from the innermost scope outward and stops at the first match.
The global scope has a special object behind it, the global object. Some global variables are properties of that object, and some are not.
- The var and function declarations at the top of a classic script become properties of the global object, and so do assignments, either to a property such as globalThis.requestCount = 0 or to an undeclared name in sloppy mode.
- The let and const declarations at the top of a classic script are global too (every classic script sees them), but they are not properties of the global object, and the same goes for class declarations.

1.1. Classic Scripts, ES Modules and CommonJS Files
Where a top-level variable lands depends on the file type, not on the keyword alone. A classic script is a plain script tag without type=”module”. An ES module is either a file loaded with type=”module” in the browser, or in Node.js an .mjs file or a .js file in a package with “type”: “module”. A CommonJS file is a Node.js .js or .cjs file that uses require(); Node.js runs it inside a module wrapper function, so its top level is function-like module scope.
| File type | Top-level var appName | Top-level let apiBaseUrl | Visible to other files |
|---|---|---|---|
| Classic browser script | Global, and window.appName exists | Global, but window.apiBaseUrl is undefined | Yes, to every classic script on the page |
| ES module (browser or Node.js) | Module scope only | Module scope only | Only through export and import |
| Node.js CommonJS file | Module scope only | Module scope only | Only through module.exports and require() |
In Node.js CommonJS modules and ES modules, top-level variable declarations are scoped to the module and are not added to the global object. For example, when a team moves a page from classic scripts to type=”module”, a top-level var appName that other scripts used to read stops being global. A TypeScript file with an import or export statement is also a module, so in a TypeScript project a top-level const is almost never global.
1.2. Top-Level var vs let and const
In a classic script, the keyword decides whether the variable also becomes a property of the global object. In Node.js, vm.runInThisContext() runs code the way a browser runs a plain script tag, so we can test classic-script behavior outside the browser too.
var appName = "Shop";
let apiBaseUrl = "/api";
const maxUsers = 10;
function greet() {}
// 1. In the browser
const onWindow = window.appName; // onWindow = "Shop"
const letOnWindow = window.apiBaseUrl; // letOnWindow = undefined
const constOnWindow = window.maxUsers; // constOnWindow = undefined
// 2. In any environment
const varProp = Object.hasOwn(globalThis, "appName"); // varProp = true
const letProp = Object.hasOwn(globalThis, "apiBaseUrl"); // letProp = false
const functionProp = Object.hasOwn(globalThis, "greet"); // functionProp = true
The let and const variables are still global, so a second classic script on the same page reads apiBaseUrl and gets “/api”. The same second script cannot declare let apiBaseUrl again, because the declaration fails with a SyntaxError (“Identifier ‘apiBaseUrl’ has already been declared”). For example, a chat widget and an analytics script from different teams both declare let apiBaseUrl on one shop page, and the script that loads second does not run at all.
A top-level var also collides with properties the browser already defines. The window object has a name property that stores only strings, so var name = 42; in a classic script gives a variable whose typeof is “string”. The same line in a module keeps the number.
1.3. Declarations Compared
Two questions decide how a declaration behaves at the top level: does it create a globalThis property, and can it be declared twice? TDZ means temporal dead zone, which is the part of the scope before the declaration line, where reading a let or const variable throws a ReferenceError (“Cannot access ‘early’ before initialization”).
| Declaration | Scope | Creates a globalThis property | Redeclare in the same scope | TDZ |
|---|---|---|---|---|
| var at the top of a classic script | Global | Yes, and delete cannot remove it | Yes | No, reads give undefined |
| function at the top of a classic script | Global | Yes | Yes | No, the function is ready before line 1 |
| let or const at the top of a classic script | Global (shared by all classic scripts) | No | No, SyntaxError | Yes |
| class at the top of a classic script | Global | No | No | Yes |
| var, let, const at the top of an ES module or CommonJS file | Module | No | Only var | Yes for let and const |
| var inside a function | Function | No | Yes | No |
| let or const inside braces | Block | No | No | Yes |
| globalThis.requestCount = 0 | Global | Yes, and delete removes it | Not a declaration, plain assignment | No |
| hits = 1 without a keyword (sloppy mode) | Global | Yes | Not a declaration | No; strict mode throws ReferenceError |
The difference in delete comes from the property flags. A top-level var is a non-configurable property of the global object, so delete globalThis.appName returns false and the value stays. A property created by assignment is configurable, so delete removes it.
2. Creating a Global Variable With globalThis
When a value must be global, we assign it to a property of globalThis once, at startup, and read it from anywhere. Before ES2020, every environment had its own name for the global object.
- Browser pages use window (also self and frames).
- Web workers use self, because window does not exist there.
- Node.js uses global.
The name globalThis works in all of them, so the same code runs in every browser and in Node.js 12.0 and later, and also in Deno and Bun. For example, a Node.js server counts the requests it handled since startup, and both the router and the health-check endpoint read the counter.
// 1. Create the global once at startup
globalThis.requestCount = 0;
// 2. Change it anywhere
globalThis.requestCount += 1;
requestCount += 1; // same variable
// 3. Read it anywhere
const total = globalThis.requestCount; // total = 2
const exists = "requestCount" in globalThis; // exists = true
// 4. One object, several names (Node.js)
const same = globalThis === global; // same = true
The bare name requestCount and globalThis.requestCount are the same variable, because every property of the global object is also a global variable. In TypeScript, the request counter code compiles only after the declaration in section 6.
In a browser page, globalThis === window is true. In browsers, globalThis refers to a proxy around the real global object (the WindowProxy) for security reasons around iframes, so the comparison works even though we never touch the inner object.
Old tutorials suggest var window.appName = “Shop”;. That line is not valid JavaScript, so Node.js and every browser reject it with a SyntaxError (“Unexpected token ‘.'”). The correct form is the assignment window.appName = “Shop”;, and globalThis.appName = “Shop”; is better because it also works outside the browser.
3. Implicit Globals and Strict Mode
An implicit global is a variable created by assigning to a name that was never declared. In sloppy mode (code without “use strict”), the engine creates a property on the global object instead of reporting an error. A typo such as requestCont = 1 then creates a second, unrelated global variable, and the bug stays hidden.
function countRequest() {
requestCount = 1; // no var, let or const
}
countRequest();
const value = requestCount; // value = 1
const onGlobal = Object.hasOwn(globalThis, "requestCount"); // onGlobal = true
Strict mode turns the same assignment into an error. ES modules and class bodies are always strict, so in modern code an implicit global fails at runtime, and TypeScript reports it at compile time.
ReferenceError: hitCount is not defined
error TS2304: Cannot find name 'total'.
One form of implicit global hides inside a normal declaration, because var local = shared = 1; declares only local. The name shared is assigned without a declaration, so it becomes a global, and local stays inside the function.
4. Shadowing a Global Variable
Shadowing happens when an inner scope declares a variable with the same name as an outer one. Inside the inner scope, the name refers to the inner variable and hides the global one, whose value does not change.
globalThis.requestCount = 5;
function handleRequest(): void {
const requestCount = 1; // local, hides the global
const local = requestCount; // local = 1
const global = globalThis.requestCount; // global = 5
}
The globalThis. prefix always reaches the global property, even when a local variable shadows it. Shadowing is legal, but it makes code harder to read, and the ESLint rule no-shadow can report it. For example, a developer adds a local requestCount for a retry loop inside a handler, and the next reader of the handler expects the global counter.
5. Better Options Than Global Variables
Global variables work, but they make a program harder to change. We use them only for values that are truly process-wide and set once, and we prefer modules for everything else. Globals cause five concrete problems.
- Any code can change the value, so a bug can come from any file.
- Two scripts or libraries can use the same name and overwrite each other.
- A function that reads a global hides its dependency; its signature does not show it.
- Tests that run in one process share the value, so one test can change the result of the next.
- The value exists only after the code that creates it has run, so load order matters.
Modules solve most of the five problems. An ES module is evaluated once, and every file that imports it gets the same exported objects, so a module already behaves like a shared, single instance. The difference from a global is that each dependency is visible in an import line.
5.1. A Config Module With Object.freeze()
For configuration, we export one object from a module and freeze it. Object.freeze() makes the properties read-only at runtime, and TypeScript types the result as Readonly, so writes fail at compile time as well.
export const config = Object.freeze({
appName: "Shop",
apiBaseUrl: process.env.API_BASE_URL ?? "/api",
featureFlags: Object.freeze({
darkMode: process.env.DARK_MODE === "true",
newCheckout: false,
}),
});
Every file that needs a setting imports config with import { config } from “./config.js”. The values are shared like a global, but nobody can change them by accident.
// 1. Read values
const name = config.appName; // name = "Shop"
const dark = config.featureFlags.darkMode; // dark = false
// 2. Writes fail
// config.appName = "Blog"; // error TS2540
// config.featureFlags.darkMode = true; // error TS2540
// 3. Shallow freeze leaves nested objects writable
const shallow = Object.freeze({ featureFlags: { darkMode: false } });
shallow.featureFlags.darkMode = true; // allowed, darkMode = true
The compiler message for both writes is “Cannot assign to ‘appName’ because it is a read-only property” (with the property name). In plain JavaScript, the write in a module throws a TypeError (“Cannot assign to read only property ‘appName’ of object ‘#<Object>'”). The freeze is shallow, so nested objects such as featureFlags need their own Object.freeze() call, as in config.ts.
5.2. A Singleton Module for Shared State
The request counter changes while the program runs, so a frozen object does not fit. We keep the variable private to a module and export functions that change and read it; other files import nextRequest() and currentCount(). No other file can reset the counter or set it to a wrong value.
let count = 0;
export function nextRequest(): number {
count += 1;
return count;
}
export function currentCount(): number {
return count;
}
nextRequest();
nextRequest();
const total = currentCount(); // total = 2
Because the module runs once, every importer sees the same count. For tests, we can add a reset() export, which is much safer than writing to a global variable from a test.
5.3. Environment Variables for Configuration
Values that differ from one environment to another, such as apiBaseUrl, belong in environment variables, not in code. Node.js exposes them on process.env, and every value is a string or undefined, so we convert flags and numbers ourselves and give each one a default.
// 1. Strings or undefined
const apiBaseUrl = process.env.API_BASE_URL ?? "/api"; // "/api" when not set
// 2. Convert flags and numbers yourself
const darkMode = process.env.DARK_MODE === "true"; // false when not set
const maxUsers = Number(process.env.MAX_USERS ?? "10"); // maxUsers = 10
Since Node.js 20.6, the –env-file option loads a file of KEY=value lines without any library. With an app.env file that contains API_BASE_URL=/api/v2 and DARK_MODE=true, the command node –env-file=app.env dist/src/index.js prints “/api/v2” and true for apiBaseUrl and darkMode. We read process.env in one place, the config module, and the rest of the code imports config.
6. Global Variables in TypeScript
TypeScript checks globals like every other variable, so an undeclared global is a compile error. Without a declaration, the request counter code from section 2 fails with strict mode on.
src/02-global-this.ts(3,14): error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
src/02-global-this.ts(7,3): error TS2304: Cannot find name 'requestCount'.
6.1. Declaring a Global With declare global
We declare each global once with declare global in a .d.ts file, and we use var inside it. A .d.ts file contains only types; it produces no JavaScript. Global augmentation adds the declaration to the global scope of the whole project.
declare global {
var requestCount: number;
}
export {};
Three details in global.d.ts matter.
- The type checker adds only var declarations to the type of globalThis. With let apiUrl: string; instead, the bare name apiUrl compiles, but globalThis.apiUrl fails with “error TS2339: Property ‘apiUrl’ does not exist on type ‘typeof globalThis'”.
- The export {} line makes the file a module. The declare global block is allowed only in a module, and without the line, tsc reports “error TS2669: Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.”
- With skipLibCheck: true in tsconfig.json, tsc skips the file without a message and does not report that TS2669 error, so the only symptom is the TS7017 error from the tsc output without a declaration, in the code that uses the global.
A .d.ts file without any import or export is a global script file, so declare var requestCount: number; at its top level declares the same global without declare global. We prefer the module form because it keeps working when someone adds an import to the file later.
The .d.ts file must be part of the compilation, so with “include”: [“src”], any file under src works.
6.2. Typing Properties on window
Browser code often reads values that a server-rendered page puts on window, such as window.appName. With the dom library enabled, TypeScript types window as Window & typeof globalThis, so an unknown property fails with “error TS2339: Property ‘appName’ does not exist on type ‘Window & typeof globalThis'”. TypeScript offers two ways to declare the property.
declare global {
// Typed on window only: window.appName
interface Window {
appName: string;
}
// Typed everywhere: featureFlags, window.featureFlags, globalThis.featureFlags
var featureFlags: { darkMode: boolean };
}
export {};
Adding a member to interface Window uses declaration merging, which means TypeScript combines our interface with the built-in one. The two forms reach different names.
| Code | interface Window { appName } | declare global { var featureFlags } |
|---|---|---|
| window.appName / window.featureFlags | Compiles | Compiles |
| globalThis.appName / globalThis.featureFlags | TS7017 | Compiles |
| appName / featureFlags (bare name) | TS2304 | Compiles |
| Works in Node.js or a web worker | No, Window is browser-only | Yes |
For a value that the page sets on window and that only browser code reads, interface Window is enough, whereas a value shared by browser and Node.js code needs declare global with var.
7. Global Variable FAQs
Most problems with globals appear when code is split across several files or runs in more than one environment.
7.1. How Do I Share a Variable Between Two JavaScript Files?
In a project with ES modules, we export the variable from one file and import it in the other. A global is not needed.
// config.ts
export const config = Object.freeze({ appName: "Shop" });
// app.ts
import { config } from "./config.js";
const name = config.appName; // name = "Shop"
Two classic script tags on one page share the global scope, so a top-level let in the first script is visible in the second, but the sharing works only as long as the script tags stay in the right order.
7.2. Why Is My Global Variable undefined in Another File?
Two causes explain most cases.
- The variable is declared at the top of an ES module or a CommonJS file, so it stays in module scope. The other file either needs an import, or the value must be assigned to globalThis.
- The code that reads the variable runs before the code that sets it. Module scripts (type=”module”) are deferred, so they run after the browser has parsed the whole page, and a normal classic script runs first and cannot read a value that a module sets.
7.3. What Is the Difference Between var appName and window.appName?
In a classic script, both create a property of the global object, and both are readable as appName and window.appName. The two forms still differ in two ways.
- A top-level var appName is non-configurable, so delete window.appName returns false, whereas window.appName = “Shop” creates a property that delete removes.
- A var declaration is hoisted, so the bare name appName gives undefined before the declaration line. With window.appName = “Shop”, the bare name appName throws a ReferenceError (“appName is not defined”) until that assignment runs.
In an ES module, var appName stays in the module and only window.appName creates a global.
7.4. Is globalThis the Same as window?
Yes, in a browser page globalThis === window is true. In a web worker, globalThis is self, and in Node.js it is global. We use globalThis in code that may run in more than one environment, and window only in code that needs browser-specific properties such as window.location.
8. Running the Example Project
The js-global-variables project contains every TypeScript snippet from this article under src, the global.d.ts declaration, the config and counter modules, plain JavaScript files that show classic-script and module behavior in Node.js, and a web folder with an HTML page and the Window typing. It uses TypeScript 7.0.2 and Node.js 22.
npm install
npm start # compile and run the TypeScript snippets
npm run start:env # the same with app.env loaded through --env-file
npm run js # classic script, CommonJS and ES module scope in plain JavaScript
npm run check:web # type-check the window typing with the dom library
The npm run js script prints the results quoted in section 1 and section 3.
--- Top-level declarations in a classic script ---
var -> true
let -> false
const -> false
function -> true
--- A second script shares the global scope ---
apiBaseUrl = /api
SyntaxError: Identifier 'apiBaseUrl' has already been declared
--- Implicit global in sloppy mode ---
requestCount = 1 | on globalThis: true
--- The same code in strict mode ---
ReferenceError: hitCount is not defined
--- CommonJS file (this file) ---
var on globalThis: false
--- ES module ---
var on globalThis: false
let on globalThis: false
To see the browser behavior, we open web/index.html in a browser and check the console.
9. Conclusion
A JavaScript global variable is a property of globalThis or a top-level declaration in a classic script. In ES modules and Node.js CommonJS files, top-level declarations stay in the module (a TypeScript file with import or export is a module too), and assigning to globalThis is the one portable way to create a global.
Strict mode makes implicit globals a ReferenceError, and TypeScript needs a declare global { var … } declaration for every global it should accept. For configuration, a frozen config module with values from environment variables keeps the data shared without the risks of globals, and a singleton module does the same for changing state such as the request counter.
10. References
MDN describes the runtime rules for the global object and each declaration, and the TypeScript documentation covers global augmentation and the typing of globalThis.
- MDN: globalThis
- MDN: Global object
- MDN: var
- MDN: let
- MDN: Strict mode
- MDN: Object.freeze()
- MDN: JavaScript modules
- Node.js: The module wrapper
- Node.js: process.env
- TypeScript Handbook: Global augmentation
- TypeScript 3.4 release notes: Type-checking for globalThis
Happy Learning !!
You shouldn’t use the ‘var’ keyword for this example because of the ‘window’ is already defined as the ‘Window’ object.
var window.iAmGlobal = “some val”;
So what is need of var here i think correct way is
window.iAmGlobal = “some val”;
hi Guys ,
I have doubt regarding global variables. Is it available on next web page.
Thanks
No. Once new page is loaded in browser, it (it’s state) is gone.
I need help to generate auto generated code for buyer like when i select India from struts2 template its code come IND01, IND02… If in case i select China code comes like CHN01, CHN02 n so on.. I am developing s/w in struts2 and hibernate 3 but i think I think we need to use javascript for this but i dont know how to use it.. Please help me out for this
Not sure what you are asking here. Please make it more clear. Not sure if you are looking for any ajax based solution.
I want to generate automated buyer code. In struts2 template I have country name. country code buyer code. these fields i have in my buyer class(Hibernate 3) all are string variable. When i select country name from country drop down i get country code(ex. India i have IND code, Chine I have CHN code) that code is display in Buyer Code text field as prefix concatenate with increment number For Ex. If i select Indian then i want buyer code as IND001, IND002….. so on. If in case i selct 3rd buyer from China then i want to display buyer code as CHN001. then CHN002….
Please help me for this . I am getting IND CHN prefix by using java script function but what about next number
Use pad function on autogenerated/ incremental numbers : https://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript
The second example has an error.
var window.iAmGlobal = “some val”;
window is already declared, no need for var.
My point is: if in your first example you were to alert the window.iAmGlobal variable it would work perfectly fine. There’s no need to further confuse the issue by incorrectly telling people this is how you globally scope a variable, which they shouldn’t even be doing in the first place.
Your 300 word post would be better served teaching hoisting, rather than ” Holy shit! var duh === var duh”.
I do not write only for experts, rather my readers are beginners and medium level experts with some hand-on. I really need to put some words to make them clear. Anyways, I got your point.
Indeed this issue does occur when you have your code split between multiple files in IE and you require them to communicate between each other. I think this IE issue isn’t preset in more recent versions so its not something that is a must have. If you’re in an enterprise environment with required support for old versions of IE this information can be useful.
True
No. None of that is correct. Your first example is the equivalent of your second when run in a browser because of variable hoisting. When you use the keyword var, JavaScript will assign that variable within the current scope. In your first example that scope is the top most, which in a browser is window.
There are tons and tons of examples of this available online, is one of the key facts thou must understand about JavaScript. If you don’t grok hoisting, your applications functioning properly is just pure dumb luck.
Thanks for your input. I understand this but the thing is behavior of browsers (specially IE) are always surprising. I myself have faced problem what I mentioned in “IE bug with “var a = foo” only declaring a global for file scope”.
So, I am concluding my learning here as always go with “windows.VAR_NAME”. Yes they are identical in a fair world, but we don’t live in that.
thanks god… no “windows” needed