completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

The old var & the global object

Understand how var and globals behave in classic scripts, why legacy code can leak names onto window, and how modules, let, const, and strict mode prevent global pollution.

By the end, you can explain
  • 01
    Function scopeWhy a var belongs to the whole function, not its block, and why it can be declared twice.
  • 02
    Two kinds of globalWhich top-level names become window properties, and which stay off window.
  • 03
    Global pollutionHow typos and clashing scripts fill the global space, and how to avoid it.

Why old globals still matter

Modern JavaScript code usually lives in modules. A module is a file loaded with import, or with <script type="module">. Modern code also makes its variables with let and const. But a lot of code you will read is older. You'll find it in analytics tags, copied browser snippets, and old libraries. That older code often does three things:

  • It makes variables with var.
  • It expects its names to show up on window.
  • It sometimes makes global names by accident.

This lesson builds on three earlier lessons:

  • The Variables lesson told you to avoid var.
  • The Execution contexts lesson showed the global execution context: the setup JavaScript makes before the first line of a script runs.
  • The Strict mode lesson showed how modern JavaScript turns silent mistakes into errors.

Now you'll see what var really does, so old code makes sense when you meet it.

The short version

A classic script is a normal <script> tag, without type="module". "Top-level" means not inside any function or block. In a classic script in the browser:

  • A top-level var or function becomes a property of the global object, window.
  • A top-level let or const is still global, but it does not become a window property.

Modules are different again. Their top-level names stay inside the module file.

Real-life analogyvar pins notices to the lobby board

Picture an office building. The building is a function. Each room is a block, like the braces of an if. Writing a var inside an if block is like pinning your notice to the lobby board, even though you wrote it in one room. Everyone in the building can read it. A let is like a note taped to your room's door: only people in that room see it. A notice in the town square is for the whole city.

In real life: A notice pinned to the lobby board
In JavaScript: A var variable, seen by the whole function
In real life: A note taped to one room's door
In JavaScript: A let or const variable, seen only inside its block
In real life: A notice in the town square
In JavaScript: A window property in a classic browser script

One note about this page. The lesson files on this site are modules. So if this page itself used a top-level var, it would not create a window property, and a typo would throw an error. To show the old behavior for real, the lab further down runs classic scripts inside a sandboxed iframe: a small, locked-down page inside this page. The tests for this lesson also check the classic-script behavior with Node's vm module, which can run code as a classic script.

var belongs to the whole function

STEP THROUGH

var has function scope. That means a var written anywhere inside a function belongs to the whole function. A block is code inside braces { }, such as the body of an if or a for. A block does not make a new scope for var. let and const are different. They have block scope: they belong only to their block.

A var is still there after its blockPop out in the code editor (opens in a new tab)JavaScript
function showCart() {  if (true) {    var item = "tea";  }  console.log(item);}showCart();

Line 3 sets item inside the if block. Line 5 is after the block, but it can still read item. That's because var puts item in the whole showCart function. The console prints tea. If line 3 used let item instead, item would exist only inside the braces, and line 5 would throw a ReferenceError.

Left: var item = tea. The box called item belongs to all of showCart, so line 5 prints tea. Right: let item = tea. The box called item belongs only to the if block, so line 5 throws a ReferenceError.var item = "tea";"tea"itemlives in all of showCartline 5 prints tealet item = "tea";"tea"itemlives only in the if blockline 5: ReferenceError
Line 3 is inside the if braces in both versions. With var, the box belongs to the whole showCart function, so line 5 finds it. With let, the box is gone at the closing brace, so line 5 throws a ReferenceError.

Now try it yourself. The next example has the same shape. Switch line 3 between var and let, then step through the code one line at a time. This small difference explains many bugs in old code.

Step through a block with var or let
Step 0 of 4Ready
Your turn: follow the blue line

Switch between var and let. Then step through to see why one name is still there after the block and the other is not.

Running in
  1. script
Next: line 1
  if (show) {    var notice = "Lobby board";  }  console.log(notice);}readNotice(true);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Switch the declaration on line 3.

Changing the keyword starts the replay again. The code and the values come from the version you picked.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

With var, line 5 can read notice after the if block. With let, line 5 is outside the block where notice lives, so it throws a ReferenceError.

The step-through also shows that a var name exists from the start of the function, set to undefined, before line 3 gives it a value. The Hoisting & the temporal dead zone lesson, coming up later, explains why. For now, remember one rule: var belongs to the nearest function around it. If there is no function, it belongs to the whole script.

The var version of the code you stepped throughPop out in the code editor (opens in a new tab)JavaScript
function readNotice(show) {  if (show) {    var notice = "Lobby board";  }  console.log(notice);}readNotice(true);

Declaring the same name twice

COMPARE

To declare a variable means to create it with var, let, or const. To redeclare it means to declare the same name again in the same scope. var allows this. The second var does not make a new variable. It reuses the same one. That made old scripts forgiving. But it also made it easy to reuse a name by mistake and never notice.

Real-life analogyTwo notices with the same title

Redeclaring with var is like pinning a second notice called "Schedule" to the lobby board. Nobody stops you, and the new notice covers the old one. With let or const, the office manager refuses the second one.

In real life: Pinning a second notice called Schedule
In JavaScript: var schedule; var schedule; is allowed
In real life: The office manager refuses a second form with the same name
In JavaScript: let schedule; let schedule; is a SyntaxError
In real life: The newer notice covers the old one
In JavaScript: A later assignment replaces the value in the same variable

let and const refuse a second declaration of the same name in the same scope. Two variables with the same name in one place would make it unclear which one you mean.

Two var lines make one variablePop out in the code editor (opens in a new tab)JavaScript
var status = "draft";var status = "published";console.log(status);

Lines 1 and 2 declare the same variable, status. Line 2 also gives it a new value. So line 3 prints published. There is only one status, not two.

var allows a second declaration; let does notPop out in the code editor (opens in a new tab)JavaScript
var status = "draft";var status = "published"; // allowed: same binding let mode = "dark";let mode = "light"; // SyntaxError before this script can run

A SyntaxError is not like other errors. JavaScript reads the whole script before it runs it. If it finds a grammar mistake, like a repeated let, it rejects the whole script and runs no line of it. Other errors, like ReferenceError, happen while the code is running.

In the iframe lab in the next section, the bad let lines sit in their own <script> tag. That way, the other scripts still run and can report the error.

globalThis & window

REAL IFRAME

The global object is an object whose properties every script can use without importing anything. The host makes it. The host is the program that runs your JavaScript, like a browser or Node.js. Each host has its own global object:

  • In a browser page, the global object is window, so globalThis === window is true. The browser also gives it other names, such as self and frames.
  • In a web worker (a script that runs in the background, apart from the page), there is no window. globalThis points at the worker's global object.
  • In Node.js, globalThis points at Node's global object.

globalThis was added to JavaScript so code can say "the global object" in every host. You don't need to guess whether the host calls it window, self, or something else.

The lab below runs a few classic scripts inside a sandboxed iframe. Next to the code, it lists what each script created. Click "Run the sandbox again" to run them once more.

Run classic scripts in a sandboxed iframe
The classic scripts inside the iframePop out in the code editor (opens in a new tab)HTML
<script>var classicCount = 1;let lexicalCount = 2;function classicTool() {}implicitName = "Ada";var name = 123;</script>
Sandbox resultsreal iframe
Waiting for the sandbox…

The sandbox sends its real classic-script results here after it runs.

Try it yourself

The iframe is a real browser page that runs classic scripts. Before this page trusts the results, it checks event.source, which says which frame sent the message. This site's own files are modules, so they could not show these classic-script effects.

The iframe is sandboxed with allow-scripts and runs only the fixed code shown. The repeated let sits in its own script tag, because a SyntaxError stops its whole script before any line runs.
Don't use name as a global

In browsers, window.name is a special property. It always stores a string. In a classic script, var name = 123; uses that same property. So the number is turned into the text "123", and typeof name is "string". Avoid global names like name: the browser already uses them.

A browser page starts with many names already on window. For example, window.status already exists and holds a string, even though modern pages rarely use it. Before you add a global name, check that the browser or another script doesn't already use it.

Two kinds of global name

SORTER

In a classic browser script, "global" can mean two different places:

  • The global object, window. Top-level var and function declarations go here, as properties.
  • A separate list of global names for top-level let, const, and class. The specification (the official rule book for JavaScript) calls this list the global lexical environment. "Lexical" means "decided by where the code is written".

Every classic script on the page can use names from both places. But only names in the first place show up as window.someName.

Which global names are on globalThisPop out in the code editor (opens in a new tab)JavaScript
var cart = "tea";let checkoutNote = "private";function checkout() {}console.log(globalThis.cart);console.log(globalThis.checkoutNote);console.log(typeof globalThis.checkout);

Line 4 finds cart as a property of globalThis and prints tea. Line 5 finds no property called checkoutNote, because a top-level let is not stored on the global object. It prints undefined. Line 6 finds checkout as a property. typeof gives the type of a value as text, so line 6 prints function.

Left: a cart called window holds two things, cart and the checkout function. They came from var and function. Right: a box called checkoutNote holds private. It came from let. It is a global name, but it is not inside window.var and functioncartcheckoutwindowon windowtop-level let"private"checkoutNotenot on window
In a classic script, var cart and function checkout become properties of window, the global object. The top-level let checkoutNote is still a global name, but it sits outside window. That is why line 5 prints undefined.

The table below lists the three places a top-level name can live. Read each row as: "this kind of name is made by this code, and here is whether you can see it on window."

Three places a top-level name can live
Where the name livesWhat code makes itIs it a window property?When to use it
The global objectA top-level var or function in a classic script, an assignment like globalThis.x = ..., or a sloppy-mode typo.Yes. In a browser you can read it as window.x.Rarely, to work with old scripts. If they need a shared entry point, use one namespace object.
The list of global let, const, and class namesA top-level let, const, or class in a classic script.No. Every script can use the name, but window.x is undefined.Small demos, or old pages that can't use modules yet.
A moduleAny top-level declaration in a module file.No. The name belongs only to that file.Modern app code. Export only what other modules need.

Now sort each declaration by one question: does it become a window property? Check whether the card says it is inside a module.

Becomes a window property?
  • var count = 1;
  • function start() {}
  • let count = 1;
  • const API = {};
  • var count = 1; inside type=module
  • function start() {} inside type=module
Try it yourself
0 of 6 correct

Sort each top-level declaration. Assume a classic browser script, unless the card says it is inside a module.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Implicit globals: a typo that makes a global

Code that is not in strict mode is often called sloppy mode. In a sloppy classic script, you can assign to a name that was never declared, and JavaScript doesn't complain. It adds that name to the global object as a new property. This is called an implicit global: a global made without any declaration. It usually starts as a typo.

A typo becomes a global in sloppy modePop out in the code editor (opens in a new tab)JavaScript
function saveUser() {  let name = "Ada";  nmae = name; // typo: creates a global in sloppy classic scripts}saveUser();console.log(globalThis.nmae);

Line 2 declares name. Line 3 misspells it as nmae. No nmae was ever declared, so in sloppy mode line 3 creates globalThis.nmae. Line 5 runs the function, and line 6 reads the new property. The console prints Ada.

The function saveUser runs the line nmae = name, a typo that was never declared. An arrow goes to a cart called window, which now holds a new card, nmae: Ada. Line 6 prints Ada.saveUser()nmae = name;typo: never declaredaddsnmae: "Ada"windowline 6 prints Ada
nmae was never declared with let, const, or var. In a non-strict classic script, line 3 quietly adds nmae to window, so line 6 prints Ada. In strict mode, line 3 throws a ReferenceError instead.

In strict mode, line 3 throws a ReferenceError instead. Modules always run in strict mode. That is one reason modules are safer.

Strict mode stops the same typoPop out in the code editor (opens in a new tab)JavaScript
"use strict";function saveUser() {  nmae = "Ada";}saveUser();

Here line 1 turns on strict mode. Line 5 calls saveUser, which runs line 3. In strict mode, an unknown name never turns into a global. So line 3 throws a ReferenceError before the function finishes. You see the typo the first time you test the code.

Strict mode helps you find bugs

If old code relies on implicit globals, adding "use strict" may show errors. That is good news: each error points at a name to fix. Declare the variable properly. Or, if an old script really needs to share something, add one property on purpose, such as globalThis.App.

Global pollution

INTERACTIVE

Global pollution means putting too many names into the shared global space. It isn't just messy. It causes real bugs:

  • Two scripts pick the same name, and one replaces the other. This is called a collision.
  • Tests pass only when the scripts load in one certain order.
  • Your app code replaces a library's function by accident.

In the demo below, two scripts both create config and $. Use the button to switch between classic scripts and modules, and watch what the app sees.

Two scripts pick the same global names
Classic scriptsPop out in the code editor (opens in a new tab)JavaScript
// library-a.jsvar config = { theme: "dark" };function $(selector) { return "A:" + selector; } // app.js, loaded latervar config = { theme: "light" };function $(selector) { return "B:" + selector; }
What the app seesnames clash
light
B:button
library-a's config and $ were replaced
Try it yourself

In classic scripts, both files write config and $ onto the same global object. The script that loads later wins.

This toggle shows a common pattern in old code: two separate files that happen to pick the same global names.
Two scripts use the same global namesPop out in the code editor (opens in a new tab)JavaScript
// library-a.jsvar config = { theme: "dark" };function $(selector) { return "A:" + selector; } // app.js, loaded latervar config = { theme: "light" };function $(selector) { return "B:" + selector; } console.log(config.theme);console.log($("button"));

Lines 2 and 3 come from the library. Lines 6 and 7 come from the app, which loads later and uses the same names. Line 9 prints light, and line 10 prints B:button. The app's versions replaced the library's.

Two script files. library-a.js loads first and sets config theme dark. app.js loads second and sets config theme light. Both arrows go to the same cart called window, which holds one config. Its theme is light, and dark is gone.library-a.jstheme: "dark"loads firstapp.jstheme: "light"loads secondconfigwindowtheme is "light""dark" is gone
Both scripts use the name config, and both write it into the same window. The script that loads second replaces the first one's value, so the page sees "light". The same happens to $.

Here are ways to fix it, from strongest to weakest:

  1. Use modules. Each file keeps its own names and shares only what it exports.
  2. Wrap old code in an IIFE (an immediately invoked function expression). That is a function that runs right away, like (function () { ... })();. Names made inside it stay inside it.
  3. Keep one shared object on purpose, such as globalThis.CompleteFrontend, and put the shared names inside it. This one object is called a namespace.
  4. Declare every name with let, const, or var, so a typo can't make a new global.

The Closure patterns lesson shows IIFEs. The Why modules? lesson, coming later, returns to this problem.

"Global means window property."

Not always. In a classic script, a top-level let or const is global, but it is not a window property.

"Modules and scripts have the same top level."

No. A module runs in strict mode, and its top-level names belong only to that file. Classic scripts all share one global space.

"Redeclaring var creates two variables."

No. The second var reuses the same variable. A later assignment changes its value.

"Implicit globals are handy shortcuts."

They are usually typos, or hidden links between scripts. Strict mode and modules turn them into errors.

"Only browsers have a global object."

Every host has one: browsers, workers, and Node.js. globalThis is the name that works in all of them.

Sharing one global on purpose

Most app code should live in modules. But sometimes an old page or a plugin needs one public entry point. Then make it easy to see:

  • Create one object on globalThis.
  • Put a small set of functions inside it.
  • Keep every helper name private.
Share one named objectPop out in the code editor (opens in a new tab)JavaScript
globalThis.CompleteFrontend = {  version: "1.0",  saveUser(name) { return "saved " + name; },}; console.log(globalThis.CompleteFrontend.saveUser("Asha"));

Line 1 creates the one shared object, CompleteFrontend. Line 3 gives it a saveUser method, the only thing this example shares. Line 6 calls it through the object, so the output is saved Asha. One object means fewer collisions. But every script can still change it, so modules are still the better choice.

The delete operator removes a property from an object. It gives true if the property is gone, and false if it could not be removed.

Which global properties delete can removePop out in the code editor (opens in a new tab)JavaScript
var oldHook = "legacy";globalThis.newHook = "intentional"; console.log(delete globalThis.oldHook);console.log(delete globalThis.newHook);console.log(globalThis.oldHook);console.log(globalThis.newHook);

Line 4 prints false. A top-level var makes a property that is non-configurable, which means it cannot be deleted. Line 5 prints true, because a property you add with = can be deleted. Lines 6 and 7 print legacy and undefined: oldHook is still there, and newHook is gone. Don't use delete to clean up a global you did not create.

The same names inside a moduleJavaScript
// module.mjsvar moduleCount = 1;let moduleMessage = "private";console.log(globalThis.moduleCount);console.log(globalThis.moduleMessage);

This example only works as a module file, so it has no Run button: the code boxes on this page run code as classic scripts. In a real .mjs file (a file Node.js loads as a module), lines 4 and 5 both print undefined. The module can use its own names, but it doesn't put them on globalThis.

Know who owns each global

If a script must share a value, write down which script owns it and what the property is called. If it must remove that value later, delete only a property your own code added with =. Then cleanup works the same way every time, even with many scripts on one page.

Practice: old globals

5 EXERCISES
Exercise 1 · Warm-upPredict what a classic script puts on globalThis

This code runs as a classic script in a browser. Predict the three lines it prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
var score = 10;
let secret = 20;
function report() {}
console.log(globalThis.score);
console.log(globalThis.secret);
console.log(typeof globalThis.report);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix a typo that makes a global

    Add strict mode and fix the typo. The function should keep the name in its own local variable instead of making a new global.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function saveUser() {
      nmae = "Ada";
      return nmae;
    }
    console.log(saveUser());

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeChange var to let in a loop

      Rewrite the function with let, so the loop's helper names don't spread across the whole function.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function totalVisible(items) {
        var total = 0;
        for (var i = 0; i < items.length; i = i + 1) {
          var item = items[i];
          if (item.visible) {
            total = total + item.price;
          }
        }
        return total;
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeExplain the window.name surprise

        In the iframe lab, a classic script runs var name = 123;. What does typeof name give?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeTurn two clashing scripts into modules

          Take the two scripts from the pollution demo. Describe how you would turn them into modules. What should each file export? What should stay private?

            Quiz: check your understanding

            8 QUESTIONS

            Answer each question from memory first. Then read every explanation. The wrong choices are real mistakes people make with old code.

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8A function has a var inside an if block. Where can the code use that var?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function demo() {
                if (true) { var label = "inside"; }
                console.log(label);
              }
              demo();

              Choose an answer to see the explanation.

            3. Question 3 of 8In a browser classic script, which top-level declaration becomes a window property?

              Choose an answer to see the explanation.

            4. Question 4 of 8What does this classic script print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              var count = 1;
              var count = 2;
              console.log(count);

              Choose an answer to see the explanation.

            5. Question 5 of 8What is an implicit global?

              Choose an answer to see the explanation.

            6. Question 6 of 8What does strict mode do to a typo assignment like nmae = "Ada"?

              Choose an answer to see the explanation.

            7. Question 7 of 8Why is global pollution dangerous?

              Choose an answer to see the explanation.

            8. Question 8 of 8What happens here in a classic script?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              Object.defineProperty(globalThis, "oldHook", { value: "legacy", configurable: false });
              globalThis.newHook = "intentional";
              console.log(delete globalThis.oldHook, delete globalThis.newHook);

              Choose an answer to see the explanation.

            Key takeaways

            • var belongs to the whole function, not to the block it is written in.
            • You can declare the same var twice, and it is still one variable. let and const refuse a second declaration in the same scope.
            • In a classic browser script, a top-level var or function becomes a window property. A top-level let or const does not.
            • In sloppy mode, assigning to a name that was never declared makes an implicit global, usually by mistake. Strict mode and modules stop this.
            • Too many globals cause collisions. Use modules first. If you can't, use an IIFE or one namespace object, and always declare your names.

            Final definition: The global object is the one object that every classic script on a page shares. Modern code keeps its names off it, unless it shares something on purpose.

            Up next: The this keyword, which explains what this means in each call.

            CompleteFrontend Clear concepts. Working examples.