JavaScript Variables

Master let, const, and var - their differences, scope rules, hoisting behavior, and the best practices used in modern JavaScript codebases.

Beginner 8 min read 10 examples

Declaring Variables

A variable is a named container for storing a value. JavaScript has three keywords for declaring variables: let, const, and var. Modern JavaScript uses let and const exclusively - var is legacy.

JavaScript
const name = "Alice";     // cannot be reassigned
let   age  = 30;          // can be reassigned
var   city = "London";    // legacy - avoid in modern code

// Declaration without initialization (let only)
let score;
console.log(score);       // undefined

// Multiple declarations
let x = 1, y = 2, z = 3;

let

let declares a block-scoped variable that can be reassigned. Use it when the variable's value will change during execution.

JavaScript
let count = 0;
count = count + 1;  // reassignment OK
count++;            // also OK
console.log(count); // 2

// let is block-scoped
if (true) {
    let insideBlock = "only here";
    console.log(insideBlock); // "only here"
}
// console.log(insideBlock); // ReferenceError - outside block

// Cannot re-declare in the same scope
let user = "Alice";
// let user = "Bob"; // SyntaxError: already declared

// But can shadow in a nested scope
let x = 1;
{
    let x = 2;       // different variable, shadows outer x
    console.log(x);  // 2
}
console.log(x);      // 1

const

const declares a block-scoped binding that cannot be reassigned. It must be initialized when declared. Use const by default - it signals the value will not change.

JavaScript
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable

// const with objects - binding is const, content is mutable
const user = { name: "Alice", age: 30 };
user.age = 31;       // OK - mutating the object
user.city = "Paris"; // OK - adding a property
console.log(user);   // { name: 'Alice', age: 31, city: 'Paris' }

// user = {};        // TypeError - cannot reassign the binding

// const with arrays
const colors = ["red", "green"];
colors.push("blue"); // OK - mutating the array
console.log(colors); // ['red', 'green', 'blue']

// To truly freeze: use Object.freeze()
const frozen = Object.freeze({ x: 1, y: 2 });
frozen.x = 99;       // silently ignored (or TypeError in strict mode)
console.log(frozen); // { x: 1, y: 2 }

var (Legacy - Avoid)

var is the original variable declaration keyword. It has two major problems compared to let/const: it is function-scoped (not block-scoped) and it is hoisted with an initial value of undefined.

JavaScript
// var leaks out of blocks (not block-scoped)
if (true) {
    var leaked = "I escape the block!";
}
console.log(leaked); // "I escape the block!" - surprise!

// var allows re-declaration (silent bug source)
var x = 1;
var x = 2; // no error - just overwrites
console.log(x); // 2

// var is hoisted with undefined
console.log(y); // undefined (not ReferenceError!)
var y = 5;

// Same behavior as:
var y;          // hoisted to top of function
console.log(y); // undefined
y = 5;
Never Use var in New Code

The function-scope and hoisting behavior of var has caused countless bugs over the years. Always use const or let. You will still see var in older tutorials and legacy codebases - understanding it helps you read that code.

Scope Rules

Scope determines where a variable is accessible. JavaScript has three types:

JavaScript
// 1. Global scope - accessible everywhere
const globalVar = "I am global";

function example() {
    // 2. Function scope - accessible inside function only
    let funcVar = "I am local";

    if (true) {
        // 3. Block scope (let/const only)
        let blockVar = "I am block-scoped";
        console.log(globalVar); // OK - outer scope visible
        console.log(funcVar);   // OK - same function
        console.log(blockVar);  // OK - same block
    }

    // console.log(blockVar); // ReferenceError
}

example();
// console.log(funcVar); // ReferenceError

Hoisting

JavaScript hoists declarations - moves them to the top of their scope during the compilation phase. var declarations are hoisted and initialized to undefined. let and const are hoisted but NOT initialized.

JavaScript
// var hoisting - initialized to undefined
console.log(a); // undefined (not ReferenceError)
var a = 5;

// let/const hoisting - in TDZ until declaration
// console.log(b); // ReferenceError: Cannot access 'b' before init
let b = 10;

// Function declarations are fully hoisted
greet(); // Works! Function is fully hoisted
function greet() {
    console.log("Hello!");
}

// Function expressions are NOT fully hoisted
// sayHi(); // TypeError: sayHi is not a function
const sayHi = function() { console.log("Hi!"); };

Temporal Dead Zone (TDZ)

The Temporal Dead Zone is the period from the start of a block scope to the point where a let or const variable is declared. Accessing it in this zone throws a ReferenceError:

JavaScript
{
    // --- TDZ starts here for 'x' ---
    console.log(typeof x); // ReferenceError (not "undefined"!)

    // --- TDZ ends here ---
    let x = 5;

    console.log(x); // 5 - now safe to access
}

// Why TDZ exists: prevents using variables before they are set.
// With var this was a silent bug: undefined when you expected a value.
// With let/const it is an explicit error: you see the problem immediately.

Naming Best Practices

JavaScript
// Use descriptive names - explain intent
const d = new Date();         // bad
const currentDate = new Date(); // good

// camelCase for variables and functions
let firstName = "Alice";
let isLoggedIn = false;       // boolean: use is/has/can prefix
function getUserById(id) {}

// SCREAMING_SNAKE_CASE for true constants
const MAX_LOGIN_ATTEMPTS = 5;
const API_BASE_URL = "https://api.example.com";

// Avoid single letters (except loop counters)
for (let i = 0; i < 10; i++) {} // i is conventional in loops

// Avoid abbreviations that are unclear
const usr = {};    // bad
const user = {};   // good

let vs const vs var - Quick Reference

Featureconstletvar
ScopeBlockBlockFunction
ReassignableNoYesYes
Re-declarableNoNoYes
HoistingYes (TDZ)Yes (TDZ)Yes (undefined)
Must initializeYesNoNo
Use in new codeDefaultWhen neededNever

Frequently Asked Questions

Use const by default. Switch to let only when you know the variable needs to be reassigned. This makes intent clear - a const variable signals "this value does not change." Most modern style guides (Airbnb, Google) recommend this approach. Never use var in new code.

No - const prevents reassignment of the variable binding, not mutation of the value. A const object can still have its properties changed: const obj = {}; obj.name = "Alice"; is valid. To make an object truly immutable, use Object.freeze(obj).

The TDZ is the period between the start of a block scope and the point where a let or const variable is declared. Accessing the variable in this zone throws a ReferenceError. This is different from var, which is hoisted and initialized to undefined before its declaration.

You can assign to an undeclared variable without a keyword in non-strict mode (x = 10), which creates a global variable. This is a bug-prone practice. In strict mode, it throws a ReferenceError. Always declare variables with const or let.