let

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since September 2016.

The let declaration declares re-assignable, block-scoped local variables, optionally initializing each to a value.

Try it

let x = 1;

if (x === 1) {
  let x = 2;

  console.log(x);
  // Expected output: 2
}

console.log(x);
// Expected output: 1

Syntax

js
let name1;
let name1 = value1;
let name1 = value1, name2 = value2;
let name1, name2 = value2;
let name1 = value1, name2, /* …, */ nameN = valueN;

Parameters

nameN

The name of the variable to declare. Each must be a legal JavaScript identifier or a destructuring binding pattern.

valueN Optional

Initial value of the variable. It can be any legal expression. Default value is undefined.

Description

The scope of a variable declared with let is one of the following curly-brace-enclosed syntaxes that most closely contains the let declaration:

Or if none of the above applies:

  • The current module, for code running in module mode
  • The global scope, for code running in script mode.

Compared with var, let declarations have the following differences:

  • let declarations are scoped to blocks as well as functions.

  • let declarations can only be accessed after the place of declaration is reached (see temporal dead zone). For this reason, let declarations are commonly regarded as non-hoisted.

  • let declarations do not create properties on globalThis when declared at the top level of a script.

  • let declarations cannot be redeclared by any other declaration in the same scope.

  • let begins declarations, not statements. That means you cannot use a lone let declaration as the body of a block (which makes sense, since there's no way to access the variable).

    js
    if (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
    

Note that let is allowed as an identifier name when declared with var or function in non-strict mode, but you should avoid using let as an identifier name to prevent unexpected syntax ambiguities.

Many style guides (including MDN's) recommend using const over let whenever a variable is not reassigned in its scope. This makes the intent clear that a variable's type (or value, in the case of a primitive) can never change. Others may prefer let for non-primitives that are mutated.

The list that follows the let keyword is called a binding list and is separated by commas, where the commas are not comma operators and the = signs are not assignment operators. Initializers of later variables can refer to earlier variables in the list.

Temporal dead zone (TDZ)

A variable declared with let, const, or class is said to be in a "temporal dead zone" (TDZ) from the start of the block until code execution reaches the place where the variable is declared and initialized.

While inside the TDZ, the variable has not been initialized with a value, and any attempt to access it will result in a ReferenceError. The variable is initialized with a value when execution reaches the place in the code where it was declared. If no initial value was specified with the variable declaration, it will be initialized with a value of undefined.

This differs from var variables, which will return a value of undefined if they are accessed before they are declared. The code below demonstrates the different result when let and var are accessed in code before the place where they are declared.

js
{
  // TDZ starts at beginning of scope
  console.log(bar); // "undefined"
  console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
  var bar = 1;
  let foo = 2; // End of TDZ (for foo)
}

The term "temporal" is used because the zone depends on the order of execution (time) rather than the order in which the code is written (position). For example, the code below works because, even though the function that uses the let variable appears before the variable is declared, the function is called outside the TDZ.

js
{
  // TDZ starts at beginning of scope
  const func = () => console.log(letVar); // OK

  // Within the TDZ letVar access throws `ReferenceError`

  let letVar = 3; // End of TDZ (for letVar)
  func(); // Called outside TDZ!
}

Using the typeof operator for a variable in its TDZ will throw a ReferenceError:

js
{
  typeof i; // ReferenceError: Cannot access 'i' before initialization
  let i = 10;
}

This differs from using typeof for undeclared variables, and variables that hold a value of undefined:

js
console.log(typeof undeclaredVariable); // "undefined"

Note: let and const declarations are only processed when the current script gets processed. If you have two