The var Declaration
Introduction
var is JavaScript's original variable declaration. It is function-scoped, can be redeclared, is hoisted and initialised to undefined, and browser-script globals declared with var become properties on the global object.
Modern JavaScript prefers let and const, but interviews still ask var constantly because it exposes core execution-context ideas: hoisting, scope, closures, and the classic loop bug.
Why This Matters
Legacy codebases, old interview snippets, and many tricky output questions still use var. If you can explain why a var loop prints the final value repeatedly, you can also explain lexical environments, closure capture, and why ES6 introduced let.
Theory
What makes var different
var is scoped to the nearest function or to the global script, not to a block. An if, for, or while block does not create a new var scope.
| Feature | var behaviour | Interview consequence |
|---|---|---|
| Scope | Function/global scoped | A var inside if is visible outside the block. |
| Hoisting | Declaration is hoisted and initialised to undefined | Reading before the line does not throw; it returns undefined. |
| Redeclaration | Allowed in the same scope | Accidental duplicate names can hide bugs. |
| Reassignment | Allowed | The binding can point to a new value. |
| Global object | Top-level browser-script var creates window.name / globalThis.name | let and const do not do this. |
Hoisting precisely
During the creation phase of an execution context, JavaScript registers var declarations and gives them the value undefined. The assignment still happens later during execution.
So console.log(x); var x = 10; behaves like var x; console.log(x); x = 10;. This is different from let and const, which are hoisted too but stay in the temporal dead zone until initialisation.
The loop closure bug
A for (var i = 0; ...) loop has one shared i binding for the whole function. If callbacks run after the loop, they all close over the same binding. By then the loop has finished, so every callback sees the final value.
When should you use it?
Almost never in new code. Use const by default and let when reassignment is needed. Learn var to read legacy code and ace interview questions, not because it is a best practice.
Visual Diagrams
Creation phase var score -> undefined Execution phase console.log(score) -> undefined score = 42 console.log(score) -> 42
The declaration is registered before execution; the assignment still runs at its original line.
for (var i = 0; i < 3; i++)
|
+-- callback A closes over same i
+-- callback B closes over same i
+-- callback C closes over same i
After loop: i === 3, so all callbacks read 3`var` does not create a fresh binding for each loop iteration.
Code Examples
Function scope, not block scope
The var binding leaks out of block statements because only functions create a new var scope.
Redeclaration and browser global caveat
At the top level of a classic browser script, var adds a property to the global object. This is not true for ES modules or Node CommonJS wrappers.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1for (var i = 0; i < 3; i++) {2 setTimeout(function () {3 console.log(i);4 }, 0);5}Predict the output #2
1console.log(score);2var score = 42;3console.log(score);Coding Exercises
Capture the current loop value with legacy var
MediumYou are maintaining legacy code that must keep var. Implement makeLoggers(n) so it returns an array of functions. Calling the returned functions should log 0, 1, ..., n - 1 instead of logging the final loop value every time.
Interview Questions
1What is hoisting for `var`, and why does reading a `var` before declaration return `undefined`?
During the creation phase of a function or global execution context, var declarations are registered and initialised to undefined. The assignment remains where it is. Therefore a read before the assignment sees the initial value undefined, not a ReferenceError. let and const are also hoisted, but they are not usable before initialisation because of the temporal dead zone.
Follow-ups
- How is this different from `let`?
- What is the temporal dead zone?
2Why does `for (var i = 0; i < 3; i++) setTimeout(() => console.log(i))` print `3` three times?
var creates one function-scoped i binding shared by every iteration. The timer callbacks close over that binding, not over a snapshot of its value. By the time callbacks run, the loop has completed and i is 3, so each callback logs 3. Use let, an IIFE, or pass the value as an argument to capture each iteration separately.
Quiz
1. Which statement about `var` is true?
Summary
- `var` is function-scoped, not block-scoped.
- `var` declarations are hoisted and initialised to `undefined`; assignments are not hoisted.
- Top-level `var` in classic browser scripts becomes a global-object property, unlike `let` and `const`.
- The classic loop closure bug happens because all callbacks share one `var` loop binding.
Cheat Sheet
Scope: nearest function or global script.
Hoisting: declaration hoisted + initialised to undefined; assignment stays put.
Allowed: redeclaration and reassignment.
Avoid in new code: prefer const, then let.
Classic bug: for (var i...) callbacks share one i; use let or an IIFE.