Skip to content

When to use let, when var in JavaScript

The common advice says: always let, never var. But each of them communicates something different about the author's intent. A note on when var still makes sense and how to choose between them.

The common advice: always let, never var. Most of the time it’s enough - but there are places where the choice between these two words carries information for the reader. This note is about those places.

#Block vs function scope

let is block-scoped - it only lives between its own { }. var is function-scoped - it reaches up to the nearest function or to the global scope:

function f() {
  if (true) {
    var a = 1;
    let b = 2;
  }
  console.log(a); // 1 - var is visible throughout the function
  console.log(b); // ReferenceError - let confined to the block
}

(I cover the scope chain mechanics in the lexical scope note, and the TDZ for let in the hoisting post.)

#Why var still exists alongside let

Because var tells the reader something that let cannot: „this variable is for the whole function”. That’s the argument from Kyle Simpson - author of the You Don’t Know JS series.

Imagine a function whose first line is let total = 0. Mechanically total is scoped to the entire function - because at the top there is no narrower block to confine it. But as a reader you don’t know that from the declaration line alone. Maybe somewhere below there is a block where total was meant to be confined? You have to scan the rest. With var total = 0 there is no such uncertainty - var says it right away: „for the whole function”.

This is a controversial stance - most style guides simply say „never use var”. Still, it’s worth knowing there’s another side to it: the argument that var and let aren’t the same thing from the reader’s perspective, even if the engine treats them identically.

#Three rules that fall out of this

In a for loop - always let. Each iteration then gets its own i instead of one shared variable across the whole loop. Without this, callbacks created inside the loop (e.g. setTimeout) read the same value - the mechanics are covered in the closure post.

A variable for the whole function - var. The reader sees it at a glance: this variable is available throughout the function, not confined to any inner block. (If the argument for var from the previous section doesn’t sit with you, use let consistently at the very top of the function instead.)

A variable needed only for a few lines - let in a standalone block. You wrap a fragment of code in { } (a block without an if, without a loop - just braces on their own), declare a variable inside, and after the block ends the variable stops existing:

function process(input) {
  // ... rest of the logic

  {
    const cleaned = input.trim().toLowerCase();
    log(cleaned);
  }

  // cleaned doesn't exist here
}

You’re creating the scope manually. The intent is visible: this variable exists only so a few lines can do their job.

#Closing thought

let and var aren’t two synonyms to choose from - they are two different signals for the reader:

  • let - „this lives briefly, in this block”
  • var - „this belongs to the whole function”

The default choice („never var”) is simple and safe. The other one (var where a variable spans the whole function, let only in blocks) demands more discipline from the author but gives the reader more in a single line. It’s a choice, not a rule from the docs.

I personally write mostly const and let. I reached for var only once - in a script in the page’s <head> that runs before React even starts. This is a standard technique in themed apps: the script reads a cookie and sets the background color before React renders anything. That way a user who prefers dark mode doesn’t see a brief flash of light when they land on the page. Such a script doesn’t go through any build - it reaches the browser exactly as I wrote it. var works in every browser, even very old ones, and that’s why I reached for it.

Interestingly, some of the variables there were visible to the whole script - and there var meant exactly what Simpson wrote about in the previous section: „this is for the whole script”.

(For what const protects - and what it doesn’t - I cover that separately in the const note.)

← Back to notes