Skip to main content

Variables

Declare variables explicitly, use one declaration per variable, group const before let, and avoid patterns that silently create globals.

13.1 Always use const or let

eslint: no-undef, prefer-const Not declaring variables with const or let results in global variables. Avoid polluting the global namespace.
const superPower = new SuperPower();

13.2 One const or let declaration per variable

eslint: one-var It’s easier to add new variable declarations this way, and you never have to worry about swapping out a ; for a , or introducing punctuation-only diffs. You can also step through each declaration with the debugger, instead of jumping through all of them at once.
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';

13.3 Group consts before lets

This is helpful when later you might need to assign a variable depending on one of the previously assigned variables.
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;

13.4 Assign variables where you need them

Assign variables where you need them, but place them in a reasonable place. let and const are block scoped and not function scoped.
function checkName(hasName) {
  if (hasName === 'test') {
    return false;
  }

  const name = getName();

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

13.5 Don’t chain variable assignments

eslint: no-multi-assign Chaining variable assignments creates implicit global variables.
(function example() {
  let a = 1;
  let b = a;
  let c = a;
}());

console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError
Chained assignment like let a = b = c = 1 makes b and c implicit globals, even inside a function. This is a subtle and dangerous bug.

13.6 Avoid unary increments and decrements

eslint: no-plusplus Unary increment and decrement statements (++, --) are subject to automatic semicolon insertion and can cause silent errors. num += 1 is more expressive and unambiguous than num++ or num ++. Disallowing them also prevents unintentional pre-increment/pre-decrement.
const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;

const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;

13.7 Avoid linebreaks around = in assignments

eslint: operator-linebreak Linebreaks surrounding = can obfuscate the value of an assignment. If your assignment violates max-len, surround the value in parens.
const foo = (
  superLongLongLongLongLongLongLongLongFunctionName()
);

const foo = 'superLongLongLongLongLongLongLongLongString';

13.8 Disallow unused variables

eslint: no-unused-vars Variables that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such variables take up space in the code and can lead to confusion by readers.
function getXPlusY(x, y) {
  return x + y;
}

const x = 1;
const y = a + 2;

alert(getXPlusY(x, y));

// 'type' is ignored even if unused because it has a rest property sibling.
// This is a form of extracting an object that omits the specified keys.
const { type, ...coords } = data;
// 'coords' is now the 'data' object without its 'type' property.

Hoisting

Understanding hoisting explains why declaration placement matters and why const and let are safer than var.

14.1 How var hoisting works

var declarations get hoisted to the top of their closest enclosing function scope, but their assignment does not. const and let declarations enter a Temporal Dead Zone (TDZ) — they are hoisted but not accessible until the declaration is reached.
// we know this wouldn't work (assuming there
// is no notDefined global variable)
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// creating a variable declaration after you
// reference the variable will work due to
// variable hoisting. Note: the assignment
// value of `true` is not hoisted.
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// the interpreter is hoisting the variable
// declaration to the top of the scope,
// which means our example could be rewritten as:
function example() {
  let declaredButNotAssigned;
  console.log(declaredButNotAssigned); // => undefined
  declaredButNotAssigned = true;
}

// using const and let
function example() {
  console.log(declaredButNotAssigned); // => throws a ReferenceError
  console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
  const declaredButNotAssigned = true;
}
With const and let, even typeof throws a ReferenceError inside the Temporal Dead Zone. This is a break from historical var behavior, where typeof on an undeclared variable safely returns "undefined".

14.2 Anonymous function expressions hoist the variable name only

Anonymous function expressions hoist their variable name, but not the function assignment.
function example() {
  console.log(anonymous); // => undefined

  anonymous(); // => TypeError anonymous is not a function

  var anonymous = function () {
    console.log('anonymous function expression');
  };
}

14.3 Named function expressions hoist the variable name only

Named function expressions hoist the variable name, not the function name or the function body.
function example() {
  console.log(named); // => undefined

  named(); // => TypeError named is not a function

  superPower(); // => ReferenceError superPower is not defined

  var named = function superPower() {
    console.log('Flying');
  };
}

// the same is true when the function name
// is the same as the variable name.
function example() {
  console.log(named); // => undefined

  named(); // => TypeError named is not a function

  var named = function named() {
    console.log('named');
  };
}

14.4 Function declarations hoist their name and body

Function declarations hoist both their name and the function body, making them callable before the declaration in the source.
function example() {
  superPower(); // => Flying

  function superPower() {
    console.log('Flying');
  }
}
Even though function declarations are hoisted, relying on this behavior makes code harder to read. Rule 14.5 requires you to define things before you use them.

14.5 Define variables, classes, and functions before using them

eslint: no-use-before-define When variables, classes, or functions are declared after being used, it harms readability since a reader won’t know what a referenced thing is. It’s much clearer for a reader to first encounter the source of a thing before encountering a use of it.
var a = 10;
console.log(a); // 10

function fun() {}
fun();

class A {
}
new A();

let a = 10;
const b = 5;
console.log(a); // 10
console.log(b); // 5

Build docs developers (and LLMs) love