JavaScript var Keyword Complete Guide
Understanding the JavaScript var Keyword
Var was the only way to declare variables in JavaScript until ES6 arrived in 2015 with let and const. For anyone writing new code, the advice is simple: don't use var - use const by default and let when you need to reassign. But var is worth understanding for two reasons: it appears throughout legacy codebases and pre-ES6 libraries, and its behavior explains exactly what problems let and const were designed to solve. The three properties that make var problematic are function scoping (not block scoping), hoisting with initialization to undefined, and the ability to redeclare the same name without any error.
Key Characteristics of var
Function scoped- A var declared inside an if block, for loop, or any block statement is accessible throughout the entire containing function - not just inside the block.Hoisted and initialized- Var declarations are moved to the top of their function during compilation and initialized to undefined. You can read a var before its declaration line without an error.Redeclaration allowed- You can declare the same var name multiple times in the same scope with no warning or error.No block scope- Blocks like if statements, for loops, and while loops don't create a new scope for var the way they do for let and const.
Basic var Declaration
The mechanics of var declaration are straightforward. You can declare and initialize in one line, declare first and assign later, or declare multiple variables in a single statement. Declaring without initialization gives the variable a value of undefined rather than throwing an error.
1// Declaration and initialization
2var userName = "John Smith";
3var userAge = 30;
4var isActive = true;
5
6// Declaration without initialization - value is undefined
7var uninitializedVar;
8console.log(uninitializedVar); // undefined
9
10uninitializedVar = "Now I have a value";
11console.log(uninitializedVar); // "Now I have a value"
12
13// Multiple variables in one statement
14var firstName = "John", lastName = "Doe", age = 25;
15console.log(firstName + " " + lastName); // "John Doe"
16
17// Redeclaration - no error, just overwrites
18var counter = 10;
19console.log(counter); // 10
20
21var counter = 20; // No SyntaxError
22console.log(counter); // 20Declaration Patterns
Initialize at declaration- Most readable option - declare and assign value on the same line.Declare then assign- Valid but the variable holds undefined between declaration and assignment.Multiple in one statement- Comma-separated declarations work but can reduce readability for longer lists.Redeclaration- Second declaration of the same name doesn't throw - it just updates the value.
Function Scoping with var
Function scoping is the property that distinguishes var from let and const most visibly. When you declare a var inside an if block or a for loop, it doesn't stay inside that block - it belongs to the enclosing function. This means you can read it after the block has closed, which is usually not what you want and is one of the behaviors that motivated the addition of block-scoped let and const.
1// var leaks out of blocks
2function demonstrateFunctionScope() {
3 if (true) {
4 var insideIfBlock = "var doesn't respect block boundaries";
5 }
6
7 // Accessible outside the if block
8 console.log(insideIfBlock); // Works
9}
10
11// Global vs function scope
12var globalVariable = "I'm accessible everywhere";
13
14function testScopes() {
15 var localVariable = "Only inside this function";
16
17 if (true) {
18 var blockVariable = "Not block-scoped - still function-scoped";
19 }
20
21 console.log(localVariable); // Works
22 console.log(blockVariable); // Also works - leaked out of if block
23 console.log(globalVariable); // Works
24}
25
26testScopes();
27console.log(globalVariable); // Works - it's global
28// console.log(localVariable); // ReferenceError - not accessible outside the functionScoping Rules
Function scope- A var declared anywhere inside a function is accessible throughout that entire function.Block boundaries ignored- if blocks, for loops, while loops - none of these create a new scope for var.Global scope- A var declared outside any function becomes a property of the global object.Scope chain- Inner functions can access var variables from outer functions through closure.
Hoisting Behavior of var
Hoisting is what JavaScript engines do during compilation: they move declarations to the top of their scope before any code executes. For var, this means both the declaration and an initialization to undefined happen before the first line of the function runs. The assignment - the actual value - stays where you wrote it. The result is that you can read a var variable before its declaration line and get undefined rather than a ReferenceError. This is different from let and const, which are hoisted but not initialized, creating the Temporal Dead Zone where accessing them before declaration throws an error.
1// Accessing var before declaration - returns undefined, not an error
2console.log(hoistedVar); // undefined
3var hoistedVar = "I was hoisted";
4console.log(hoistedVar); // "I was hoisted"
5
6// What the engine actually does:
7// var hoistedVar; // Declaration moved to top, initialized to undefined
8// console.log(hoistedVar); // undefined
9// hoistedVar = "I was hoisted"; // Assignment stays here
10// console.log(hoistedVar); // "I was hoisted"
11
12// Hoisting scoped to the function
13var a = 1;
14function hoistingScope() {
15 console.log(a); // undefined - not 1
16 // The local var a is hoisted to the top of this function,
17 // shadowing the outer a before the assignment runs
18 var a = 2;
19 console.log(a); // 2
20}
21hoistingScope();
22console.log(a); // 1 - outer var unchanged
23
24// let comparison - TDZ instead of undefined
25// console.log(letVariable); // ReferenceError: Cannot access before initialization
26let letVariable = "not hoisted the same way";Redeclaration and Reassignment
Var allows both redeclaration (using var with the same name twice in the same scope) and reassignment (changing the value). The redeclaration is the more dangerous one in practice - in large files it's easy to accidentally reuse a variable name without realizing a var with that name already exists, and JavaScript gives you no warning. The accidental redeclaration bug shows up as a variable that suddenly has a different value with no obvious cause.
1// Redeclaration - no error, second declaration overwrites
2var count = 5;
3console.log(count); // 5
4
5var count = 10; // No SyntaxError - just updates value
6console.log(count); // 10
7
8// Reassignment - same as let
9var price = 100;
10price = 200; // Plain reassignment
11console.log(price); // 200
12
13// Shadowing: local var with same name as outer var
14var globalVar = "I'm global";
15
16function shadowExample() {
17 var globalVar = "I'm local"; // Creates a new local variable, shadows the global
18 console.log(globalVar); // "I'm local"
19}
20
21shadowExample();
22console.log(globalVar); // "I'm global" - the global is unchanged
23
24// Accidental redeclaration - silent bug
25var total = 0;
26// ... much later in a large file ...
27var total = 100; // Programmer forgot 'total' was already declared
28console.log(total); // 100 - original value gone, no warningCommon Patterns and Pitfalls
The loop closure problem is the most famous var pitfall and worth understanding thoroughly. When you create functions inside a for loop that use the loop variable, var's function scope means all those functions share a single variable. By the time the callbacks run, the loop has finished and the variable holds its final value - typically the loop limit, not the values from each iteration. The fix in pre-ES6 code was an IIFE to create a new scope per iteration. In modern code, you just switch to let.
1// The closure problem: all callbacks log 3
2var functions = [];
3for (var i = 0; i < 3; i++) {
4 functions.push(function() {
5 console.log(i); // 'i' is shared across all iterations
6 });
7}
8
9functions[0](); // 3
10functions[1](); // 3
11functions[2](); // 3
12
13// Pre-ES6 fix: IIFE creates a new scope per iteration
14var fixedFunctions = [];
15for (var i = 0; i < 3; i++) {
16 (function(j) { // 'j' is captured per-iteration
17 fixedFunctions.push(function() {
18 console.log(j);
19 });
20 })(i);
21}
22
23fixedFunctions[0](); // 0
24fixedFunctions[1](); // 1
25fixedFunctions[2](); // 2
26
27// Modern fix: just use let
28var letFunctions = [];
29for (let i = 0; i < 3; i++) {
30 letFunctions.push(function() {
31 console.log(i); // Each iteration has its own 'i'
32 });
33}
34
35letFunctions[0](); // 0
36letFunctions[1](); // 1
37letFunctions[2](); // 2
38
39// Variable shadowing gotcha with hoisting
40var x = "global";
41
42function shadowGotcha() {
43 console.log(x); // undefined - not "global"
44 // The local var x is hoisted, shadowing the outer x before assignment
45 var x = "local";
46 console.log(x); // "local"
47}
48
49shadowGotcha();
50console.log(x); // "global"var vs let vs const
The practical difference in everyday code: scope behavior and what happens before the declaration line. Var is function-scoped and gives you undefined before its declaration. Let and const are block-scoped and throw a ReferenceError before their declarations. Var allows redeclaration with no warning - let and const throw SyntaxError. Const additionally prevents reassignment and requires initialization. If you're reading old code, expect to see var everywhere. If you're writing new code, use const or let.
1// Scope: var leaks, let/const don't
2function scopeComparison() {
3 if (true) {
4 var varVariable = "function scoped";
5 let letVariable = "block scoped";
6 const constVariable = "block scoped";
7 }
8
9 console.log(varVariable); // Works
10 // console.log(letVariable); // ReferenceError
11 // console.log(constVariable); // ReferenceError
12}
13
14// Hoisting: var gives undefined, let/const throw
15console.log(varHoisted); // undefined
16var varHoisted = "hoisted";
17
18// console.log(letHoisted); // ReferenceError (TDZ)
19let letHoisted = "not the same";
20
21// Redeclaration: var allows it, let/const don't
22var canRedeclare = "first";
23var canRedeclare = "second"; // No error
24
25let noRedeclare = "first";
26// let noRedeclare = "second"; // SyntaxError
27
28// Initialization: const requires it
29var canBeUndefined; // Fine
30let alsoFine; // Fine
31// const mustHaveValue; // SyntaxError: Missing initializerBest Practices and Legacy Code
When writing new code, the answer is easy: don't use var. When working with legacy code, var is everywhere and understanding it is necessary. The patterns that appeared in pre-ES6 code to work around var's limitations are worth recognizing: declaring all vars at the top of the function (to make hoisting explicit rather than implicit), IIFEs to create private scope, and the namespace object pattern to reduce global pollution. When refactoring legacy var code to const/let, the main risk is that block scoping changes can reveal accidental accessibility - a var that was leaking out of a block and being read somewhere it shouldn't have been.
1// Pattern 1: Declare all vars at top of function
2// (makes hoisting explicit, was recommended pre-ES6 style)
3function goodVarUsage() {
4 var count = 0,
5 name = "John",
6 isActive = true;
7
8 if (isActive) {
9 count++;
10 console.log(name + " is active");
11 }
12
13 return count;
14}
15
16// Pattern 2: IIFE to contain scope and avoid globals
17(function() {
18 var privateVar = "not global";
19})();
20
21// Pattern 3: Namespace object to reduce global variables
22var MY_APP = MY_APP || {};
23MY_APP.config = {
24 apiUrl: "https://api.example.com",
25 timeout: 5000
26};
27
28// Pattern 4: Strict mode to catch accidental globals
29function strictFunction() {
30 "use strict";
31 // accidentalGlobal = "error"; // ReferenceError in strict mode
32 var properLocal = "correct";
33}
34
35// Modern equivalents for new code
36const PI = 3.14159; // was: var PI = 3.14159;
37let counter = 0; // was: var counter = 0;
38const CONFIG = {}; // was: var CONFIG = {};