let vs. var vs. const

Three ways to declare JavaScript variables—one good default, one useful alternative, and one mostly for legacy code.

Definitions

let

Declares a block-scoped variable whose value can be reassigned.

var

Declares a function-scoped or global variable that can be reassigned and redeclared.

const

Declares a block-scoped binding that must be initialized and cannot be reassigned.

At a glance

Behavior const let var
Scope Block Block Function or global
Reassignment No Yes Yes
Redeclaration in the same scope No No Yes
Initialization required Yes No No
Before its declaration Temporal dead zone Temporal dead zone Reads as undefined

Block scope matters

let and const stay inside the nearest block. var ignores ordinary blocks and remains visible throughout the containing function.

if (true) {
  const message = "inside";
  let count = 1;
  var legacy = "still visible";
}

console.log(legacy); // "still visible"
console.log(count);  // ReferenceError

Hoisting and the temporal dead zone

All three declarations are processed before execution reaches them. The difference is initialization: var starts as undefined, while let and const cannot be accessed until their declaration runs.

console.log(oldValue); // undefined
var oldValue = 10;

console.log(newValue); // ReferenceError
let newValue = 10;

Why does the temporal dead zone occur?

When a scope is created, JavaScript creates its let and const bindings but leaves them uninitialized. The temporal dead zone is the time between entering that scope and executing the declaration. Reading the binding during that interval throws a ReferenceError.

This behavior makes block-scoped shadowing consistent across the entire block and exposes use-before-initialization bugs. It also ensures that a const binding receives its required initial value exactly at its declaration. By contrast, var is initialized to undefined as soon as its function scope is created.

const does not make values immutable

const protects the binding, not the object stored in it. Object properties and array contents may still change.

const user = { name: "Ada" };
user.name = "Grace";       // allowed

user = { name: "Linus" };  // TypeError

Choosing one

const apiUrl = "/api/users"; // binding stays the same

let page = 1;
page += 1;                   // binding changes

// Use var only when maintaining code that already depends on it.

Code output questions

Predict the result before opening each answer. Some snippets test behavior that is easy to miss.

  1. What is printed?

    console.log(total);
    var total = 5;
    Show answer

    undefined. The var declaration is hoisted and initialized before the assignment runs.

  2. Does this print "outer"?

    let label = "outer";
    
    {
      console.log(label);
      let label = "inner";
    }
    Show answer

    No. It throws a ReferenceError. The inner label shadows the outer one throughout the block, but remains in its temporal dead zone until its declaration runs.

  3. What is printed after the loop finishes?

    for (var i = 0; i < 3; i++) {
      setTimeout(() => console.log(i), 0);
    }
    Show answer

    3, three times. Every callback closes over the same function-scoped i, and the callbacks run after the loop has changed it to 3. Replacing var with let would create a new binding for each iteration and print 0, 1, 2.

  4. What is printed?

    const profile = { name: "Ada" };
    profile.name = "Grace";
    
    console.log(profile.name);
    Show answer

    "Grace". The object can be mutated; only reassignment of the profile binding is forbidden.

  5. What are the two outputs?

    function inspect() {
      if (true) {
        var message = "ready";
        let count = 1;
      }
    
      console.log(message);
      console.log(typeof count);
    }
    
    inspect();
    Show answer

    "ready", then "undefined". var escapes the if block because it is function-scoped. count is outside its scope, and typeof on an undeclared name returns "undefined".

  6. Why is the global value not printed?

    var mode = "global";
    
    function show() {
      console.log(mode);
      var mode = "local";
    }
    
    show();
    Show answer

    It prints undefined. The local var mode is hoisted to the top of show, shadows the global binding, and is initialized to undefined before the log runs.

  7. Does either line run?

    const answer;
    console.log("finished");
    Show answer

    No. Parsing fails with a SyntaxError because const requires an initializer. The program never begins executing.