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.

javascript
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); // 20

Declaration 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.

javascript
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 function

Scoping 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.

javascript
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.

javascript
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 warning

Common 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.

javascript
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.

javascript
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 initializer

Best 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.

javascript
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 = {};

JavaScript var Keyword FAQ

What is the main difference between var and let?

Var is function-scoped and hoisted with initialization to undefined. Let is block-scoped and hoisted but not initialized - accessing it before its declaration line throws a ReferenceError rather than silently returning undefined. Var can be redeclared in the same scope without error; let cannot. Both allow reassignment.

Why should I avoid var in new code?

Var's function scoping causes variables to leak out of if blocks and loops, which is almost never what you want. Its hoisting with undefined initialization silently allows reading before assignment. Its redeclaration permission can silently overwrite variables without warning. All three properties make bugs easier to introduce and harder to find. Let and const don't have any of these problems.

Can I still use var in modern JavaScript?

Yes, it still works - var has been part of JavaScript since the beginning and isn't going anywhere. The language won't break if you use it. The issue is purely about code quality: var's behavior makes it easier to write buggy code than let and const do. Modern linters will flag var usage and suggest replacements.

What is hoisting and how does it affect var?

During compilation, JavaScript moves variable and function declarations to the top of their scope before executing any code. For var, this means the declaration and an initialization to undefined happen before the first line runs. The assignment stays where you wrote it. So reading a var before its declaration line gives you undefined rather than a ReferenceError, which can mask bugs.

Why do var variables in loops cause issues with closures?

Because var is function-scoped, all iterations of a for loop share one variable - not one per iteration. When you create a function inside the loop (like a callback) that references the loop counter, all those functions reference the same variable. By the time they run, the loop has ended and the variable holds its final value. This is why all the callbacks log the same number.

How can I fix loop closure issues with var?

The clean fix is to use let instead of var - let creates a new binding per iteration. The pre-ES6 fix was an IIFE: wrapping the loop body in an immediately invoked function that takes the current counter value as a parameter, creating a new scope per iteration with its own captured value.

What happens if I declare the same var name twice?

Nothing visibly wrong - no error, no warning. The second declaration acts as a reassignment. This is the silent danger of var: you can introduce a naming collision in a large file and JavaScript will happily let both declarations exist, with the second one winning. With let or const you'd get a SyntaxError immediately.

When would I actually need to use var?

Practically: when maintaining legacy code, where changing var to let/const could unintentionally change behavior (particularly around scope boundaries). There's also a rare case where function-scoping is specifically needed, but this is unusual enough that it's worth asking whether the design could be changed instead.