cf.completefrontendCode editorOpen lab
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 var ignores blocks and can be redeclared.
  • 02
    Two kinds of globalWhich top-level names become window properties and which stay lexical.
  • 03
    Global pollutionHow implicit globals and script collisions happen, and how to avoid them.

Why old globals still matter

Modern JavaScript usually lives in modules, uses let and const, and avoids the global object. But a lot of code you will read is older: analytics tags, browser snippets, old libraries, and pre-bundler applications. That code often uses var, expects names to appear on window, and sometimes creates globals by accident.

This lesson connects three ideas you have already met: the Variables lesson warned you to avoid var, the Execution contexts lesson introduced the global execution context, and the Strict mode lesson showed how modern JavaScript catches silent mistakes. Now we go deeper so legacy code makes sense.

The short version

In a browser classic script, top-level var and top-level function declarations become properties of the global object: window. Top-level let and const are still global to scripts, but they are not window properties. Modules are different again: their top level belongs to the module.

Real-life analogyvar pins notices to the lobby board

A var declared inside an if block is like a notice pinned to the whole building’s lobby board instead of your room’s door. Anyone on that floor of the building—the whole function—can see it, even if the notice was written while standing inside one room.

In real life: A notice pinned to the building lobby
In JavaScript: A var binding visible across the whole function
In real life: A note taped to your room door
In JavaScript: A let or const binding visible only in its block
In real life: A public city square notice
In JavaScript: A window property in a classic browser script

Where the analogy stops: Scope is not a physical place, and JavaScript decides visibility from the source code structure, not from where a value is stored in memory. The analogy is about who can see the name.

Keep one honesty rule in mind: this site’s lesson files are ES modules. If we wrote top-level var directly in this component, it would not create a window property, and a typo assignment would throw. The classic browser behavior below runs for real inside a sandboxed iframe, and the tests also prove classic-script behavior with Node’s vm classic scripts.

Function-scoped var

STEP THROUGH

var has function scope. A var declared anywhere inside a function belongs to that entire function. Blocks made by if, for, or plain braces do not create a new scope for var. let and const do use block scope.

Switch line 3 between var and let. The code is tiny, but it explains a huge number of legacy bugs.

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

Switch var to let, then step through why one name leaks out of the block and the other does not.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  if (show) {    var notice = "Lobby board";  }  console.log(notice);}readNotice(true);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Switch the declaration on line 3.

Changing the keyword starts a fresh replay. The displayed source and values are generated from the real branch.

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 binding’s block and throws a ReferenceError. The upcoming Hoisting & the temporal dead zone lesson explains the creation phase details; for now, remember the practical rule: var belongs to the nearest function, or to the global script if there is no function.

The source you just 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);

Redeclaration: var shrugs, let objects

COMPARE

In the same scope, var can be declared again. It does not create a fresh variable; it reuses the same binding. That made old scripts forgiving, but it also made collisions easy to miss.

Real-life analogyTwo notices with the same title

Redeclaring var is like pinning a second notice with the same title to the public board. var shrugs. let and const object because two block-scoped bindings with the same name in the same scope would make the code ambiguous.

In real life: Pinning a second notice titled Schedule
In JavaScript: var schedule; var schedule; is allowed
In real life: The office manager rejects a duplicate private form
In JavaScript: let schedule; let schedule; is a SyntaxError
In real life: The newer notice may cover the old wording
In JavaScript: A later assignment overwrites the same binding

Where the analogy stops: A redeclaration is a syntax rule, not a document filing system. The point is that var tolerates duplicates in one scope, while lexical declarations protect you from them.

Redeclaration contrastPop 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 different from a runtime error: JavaScript rejects that script before executing it. In the iframe lab, the bad let redeclaration is placed in its own classic script tag so the page can still report the error afterward.

globalThis & window

REAL IFRAME

The global object is the object a JavaScript host provides for globally available properties. In a browser page, that object is exposed through window, and globalThis === window is true in ordinary page scripts. The browser also has aliases such as self and frames. In workers, there is no window, but globalThis still points at the worker’s global object. In Node.js, globalThis points at Node’s global object.

globalThis was standardized so code could say “the global object” without guessing whether the host calls it window, self, or something else.

Run classic scripts in a sandboxed iframe
Classic script snippets 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 iframe…

The sandbox will post real classic-script results after it runs.

Try it yourself

The iframe is a real browser classic-script environment. The page code validates event.source before trusting the posted results. This site's ES modules would not show these classic-script leaks.

Sandboxed with allow-scripts; no user code is evaluated. The let redeclaration is intentionally isolated in its own script tag because a syntax error stops that script before it can run.
A browser quirk worth knowing

window.name is special in browsers. It stores a string. In a classic script, var name = 123; interacts with that property, so typeof name is "string" and the value behaves like "123". Avoid global names like name; they may already mean something to the host.

The global object vs global lexical bindings

SORTER

“Global” has two layers in classic browser scripts. The global object record handles names that are properties of window. The global lexical environment handles top-level let, const, and class. Both are global for script name lookup, but only one is posted in the public square as window.someName.

Three places top-level names can live
KindCreated byVisible as property?Best use
Global object propertyClassic-script var, classic-script function declarations, explicit globalThis.x = ..., and sloppy implicit globalsYes: window.x in browsersRare legacy interop. Prefer a single namespace if old scripts need one shared hook.
Global lexical bindingTop-level let, const, and class in a classic scriptNo: the name is global to scripts, but not window.xSmall demos or old pages that cannot use modules yet.
Module bindingTop-level declarations in an ES moduleNo: module top level is file-scopedModern application code. Export only what other modules need.

Sort each declaration by whether it becomes a window property. Pay attention to classic scripts versus modules.

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

Classify each top-level declaration. Assume a browser 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: graffiti on the public square

In sloppy classic scripts, assigning to a name that was never declared creates a property on the global object. That is an implicit global. It often starts as a typo.

A typo that becomes public in sloppy scriptsPop out in the code editor (opens in a new tab)JavaScript
function saveUser() {  let name = "Ada";  nmae = name; // typo: creates window.nmae in sloppy classic scripts}

That typo is graffiti: a misspelled assignment quietly paints a new name on the public square. In strict mode, the same line throws a ReferenceError. ES modules are always strict, which is one reason they are safer by default.

Strict mode is a bug detector

If old code depends on implicit globals, adding "use strict" may reveal errors. That is good: declare the variable explicitly, or attach one intentional property such as globalThis.App if a legacy script truly needs a shared namespace.

Global pollution

INTERACTIVE

Global pollution means putting too many names into shared global space. The danger is not just messiness. It is collisions between scripts, tests that pass only in one order, and app code that accidentally overwrites a library.

Watch two scripts fight over 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 seespolluted
light
B:button
A's globals were overwritten
Try it yourself

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

This is a conceptual toggle over a common legacy pattern: two independent files accidentally choosing the same public names.
The classic collision as plain codePop 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"));

Fixes, from strongest to weakest: use modules; wrap old code in an IIFE; keep one deliberate namespace such as globalThis.CompleteFrontend; and declare every name with let, const, or var. The Closure patterns lesson mentions IIFEs, and the future Why modules? lesson returns to this problem from a module-system angle.

“Global means window property.”

Not always. Top-level let and const in classic scripts are global lexical bindings, not window properties.

“Modules and scripts have the same top level.”

No. Modules are strict and scoped to the module file. Classic scripts share a global script environment.

“Redeclaring var creates two variables.”

No. It reuses the same binding in the same scope. A later assignment changes that binding.

“Implicit globals are convenient shortcuts.”

They are usually typos or hidden dependencies. Strict mode and modules reject them.

“Only browsers have global objects.”

Every JavaScript host has one. globalThis is the cross-host way to refer to it.

Practice: legacy globals

5 EXERCISES
Exercise 1 · Warm-upPredict the classic script globals

In a browser classic script, predict the three lines printed by this code.

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 an implicit global bug

    Add strict mode and fix the typo so the function keeps the name local instead of painting 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 · PracticeConvert a var leak to block scope

      Rewrite the function with let so the loop helper names do not leak 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 quirk

        In the iframe lab, var name = 123; leads to typeof name being what?

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

          Exercise 5 · ChallengeRefactor two conflicting global scripts

          Take the two-script collision from the pollution demo and describe how you would refactor it to modules. What should be exported? What should stay private?

            Quiz: check your understanding

            7 QUESTIONS

            Answer once from memory, then read every explanation. The wrong choices describe real misconceptions in legacy code.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is the scope of var inside an if block in a function?

              Choose an answer to see the explanation.

            2. Question 2 of 7What 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 7In a browser classic script, which top-level declaration becomes a window property?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this classic-script pair 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 7What is an implicit global?

              Choose an answer to see the explanation.

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

              Choose an answer to see the explanation.

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

              Choose an answer to see the explanation.

            Key takeaways

            • var is function-scoped, not block-scoped.
            • var redeclaration is allowed; let and const reject duplicate declarations in the same scope.
            • In browser classic scripts, top-level var and functions become window properties; top-level let and const do not.
            • Sloppy implicit globals are accidental global object properties. Strict mode and modules stop them.
            • Global pollution causes collisions. Prefer modules, IIFEs, namespaces, and explicit declarations.

            Remember the one-liner.
            The global object is the public square; modern code keeps most names off the square unless sharing is intentional.

            Up next: The this keyword.

            CompleteFrontend Clear concepts. Working examples.