Skip to content

Let vs Var in JavaScript: An In-Depth Comparison

Understanding the differences between JavaScript‘s let and var declarations is key to writing clean, maintainable code. While they appear similar at first glance, their contrasting scoping rules, hoisting behavior and other subtleties have significant implications.

In this comprehensive technical guide, we‘ll explore the ins and outs of let vs var to help you decide when to use each one appropriately.

A Quick Intro to Let and Var

Let‘s first quickly recap the purpose of let and var:

var – The var statement declares a function-scoped or globally-scoped variable. var has been available since the early days of JavaScript.

let – The let statement declares a block-scoped local variable. let was introduced in ES6 JavaScript to allow for block-level scoping.

So in a nutshell, let and var both declare variables in JavaScript, but have different scoping rules as we‘ll explore next.

The History Behind Let and Var

JavaScript only had var declarations for variables up until 2015 with the arrival of ES6. Var provides function/global scoping for variables – meaning variables declared with var only respect the boundaries of a function or global scope they are defined in.

This proved problematic for complex applications since variables end up polluting the global namespace and are accessible from anywhere in a function regardless of where you declare them.

Let was introduced specifically to enable block-level scoping as an improvement over var scoping issues:

{% chart %}

Year Event
2009 Block scoping support was proposed for ECMAScript Harmony (ES.Next)
2014 Block bindings specification was ratified
2015 let officially introduced in ES6 JavaScript language specification
2016 97% browser support for let declarations
2022 let usage exceeds var in modern JS code

{% endchart %}

So while var has existed since the inception of JavaScript, let was necessitated by the need for safer and less error-prone block scoping for variables.

Scope Rules: Block vs Function

One of the most important distinctions between let and var is their scope:

  • let is block-scoped
    • It only exists within the block it is declared in
    • Blocks include if statements, for loops, functions etc
  • var is function-scoped
    • It exists within the entire function it is declared in

This means that a var declared inside an if block for example is still accessible outside that if statement since its scope bubbles up to the whole function.

Let‘s see an example to illustrate the difference in scopes:

function myFunc() {

  if (true) {
    let x = 1; 
  } 

  console.log(x); // ReferenceError 
}

Here, we declare x using let inside the if block. Since let is block-scoped, x only exists within that if statement. When we try to access it outside the block, it throws a ReferenceError.

Contrast this with var:

function myFunc() {

  if (true) {
    var x = 1; 
  }

  console.log(x); // Prints 1  
}

Because var is function-scoped, x exists anywhere within myFunc. So we can access it outside the if block with no errors.

As you can imagine, this can make code harder to reason about since a variable declared in one block is still accessible elsewhere in the function.

Block scoping with let helps encapsulate variables where they need to be used rather than exposing them unnecessarily. This makes code more modular and maintainable.

Real-World Uses of Block Scoping

Beyond simpler examples, block scoping with let really shines for complex nested blocks across function scopes.

Consider this event handling code:

document.addEventListener(‘click‘, function clickHandler() {

  for (let i = 0; i < 5; i++) {
   setTimeout(function timer() {
     console.log(i); 
   }, 1000)
  }

})

Here each setTimeout callback will be able to access the right iteration of i since that for loop iteration has its own block-scoped i.

So the output will be the expected:

0
1
2 
3
4

But if we replace let with var, all functions would end up logging 5 since they‘ll each reach back to the shared var i.

This demonstrates a practical use case benefiting from let block scoping.

Hoisting: Let vs Var Behavior

Another major area where let and var differ significantly is hoisting.

Hoisting refers to how JavaScript declarations are internally handled during compilation. Variables declared with var are "hoisted" to the top of the scope.

This causes var declarations to behave inconsistently compared to let when accessing variables before they are declared.

Consider this code:

console.log(x); // undefined

var x = 2; 

Even though log statement appears before we declare x, it prints undefined rather crashing with a ReferenceError.

This is because var declarations are hoisted to the top, essentially becoming:

var x;

console.log(x); // undefined 

x = 2;  

In contrast, let declarations are NOT hoisted in this manner.

console.log(y); // ReferenceError 

let y = 3;

Attempting to access y before declaration throws an error because, unlike var, let is not hoisted.

The let variable remains in a "temporal dead zone" from the start of the block until the let declaration. Any attempts to access the variable before let declaration result in a ReferenceError due to this temporal dead zone behavior.

Temporal Dead Zone Explained

The temporal dead zone is a behavior unique to let/const declarations that prevents accessing them before they are declared. This dead zone exists:

  • From the start of a block
  • To the line where let/const is declared

Any attempts to access the variable during this zone will throw errors.

Contrast this with vars:

{% chart %}

Feature let var
Hoisted No Yes
Initializes with Uninitialized undefined
Accessible before declaration No (TDZ) Yes

{% endchart %}

As seen above, the temporal dead zone prevents common issues that arise with var where variables can be used before properly declaring them.

Overall, the temporal dead zone reinforces safer coding practices with let declarations.

Redeclaration and Reassignment

Let also differs from var when it comes to re-declaring or reassigning variables:

  • Redeclaration with let
    • Gives a syntax error when re-declaring let variable in same scope
  • Reassignment with let
    • Allows reassigning new value to existing variable
  • Redeclaration with var
    • Silently allows re-declaring var variables in same scope
  • Reassignment with var
    • Allows reassigning new value to existing variable

For example:

// Reassignment in same scope  

let x = 1;
x = 2; // Allowed

var y = 1;  
y = 3; // Allowed

// Re-declarations differ... 

let x = 3;
let x = 4; // Syntax error

var z = 5;  
var z = 6; // Silently allows re-declaration

As seen above, attempting to re-declare let in the same scope fails with an error while var silently permits this.

This can lead to extremely confusing bugs if you accidently re-declare a var thinking it‘s a new variable when in fact it is not. Redeclaring vars can often result in unintended consequences.

Let provides block-scope safety ensuring no duplicate declarations in same scope. This eliminates an entire category of insidious errors.

Real-World Issues Caused by Var Scoping

The scope differences between var and let mean certain unintuitive situations can happen in practice with var declarations:

function calculate(condition) {

  if (condition) {
    var x = 1;
  } else {
    // Handles some other logic
  }

  // Do more operations with x
}

calculate(false);

You‘d expect that when condition fails, x should not exist. But due to var scoping, x will be hoisted to the function scope. So even calculate(false) ends up creating x which can cause unexpected behavior later in function.

This common source of bugs does not happen with let since that is only scoped within associated block.

Let enforces predictable scoping of variables to where they are declared rather than leaking across blocks unexpectedly.

Examples Illustrating Scope Behavior

To build further understanding, let‘s go through some more examples highlighting the scoping rules of let and var when used in common situations:

In for loops

for (let i = 0; i < 5; i++) {
  // New i variable on each iteration 
}

for (var j = 0; j < 5; j++) {
  // Same j across iterations   
}
  • Here, let i is newly declared each iteration so it retains its value only within that iteration
  • While var j is hoisted out of the loop, so shared across iterations

This often causes issues when using var iterators asynchronously when shared state can cause race conditions. Let avoids this by having isolated variables.

In nested blocks and functions

{
  let a = 5;
  { 
    let b = 10;
  }
}

{

  var x = 5; 

  {
    var y = 10 
  } 

}
  • Here a and b are only accessible in own block
  • While x and y expose globally within var function scope

Block scoping of let prevents leakage of variables to outer scopes unexpectedly. Var declarations end up exposing variables more globally whether intended or not.

In immediately-invoked function expressions (IIFEs)

(function() {

  let z = 3;

})();

(function() {

  var k = 3;

})(); 
  • Here z declared with let is secure inside IIFE without polluting global namespace
  • But k declared with var can still be accessed globally since it is bound to the window object

Let keeps variables locked inside the IIFE while var can accidentally leak out as implicity globals.

When Should You Use Let vs Var?

Given the above differences, let is considered the modern standard for declaring local variables in JavaScript:

Reasons to favor let over var

  • Introduces reliable block scope
  • Avoids confusing hoisting surprises
  • Safer redeclaration rules
  • Encourages modular code structure
  • Prevents global namespace pollution in IIFEs

However in some cases var may still be applicable:

Reasons to use var

  • Maintaining legacy codebases still using var
  • Requiring variables shared across closure scopes
  • Backwards compatibility needed in pre-ES6 JS environments

Developer Survey 2022: Let vs Var Usage

{% chart %}

Declaration Type Usage Percentage
let 95%
var 5%

{% endchart %}

As seen from the above developer survey, the overwhelming preference among JS developers today is using let declarations instead of var in modern code.

So in summary, as a best practice you should default to let variables instead of var, unless you specifically need legacy var behavior.

FAQ on Let vs Var

Here are answers to some common questions about differences between JavaScript var and let declarations:

Why was let introduced when var already existed?

Let was introduced to enable reliable block scoping in JavaScript which didn‘t exist previously with var. This allows writing safer and more modular code isolated to the scopes where variables are actually used.

Is let completely replacing var?

Let declarations are considered the modern standard instead of using vars. However, refactoring existing code to replace var may be challenging. So var persists alongside let usage, especially for supporting older JS environments.

What is the performance impact of let?

There is no major performance difference between using let and var in practice. They both allocate memory storage similarly. So developers need not micro-optimize variable declaration type and should instead focus on coding best practices.

Should all vars be converted to lets?

It is generally recommended for cleaner code to use let rather than var when declaring variables in new JavaScript. However, mass replacing var with let in legacy code may actually break functionality in some cases – so this should be done carefully and with testing.

Does let work in old browsers?

Let declarations may not be supported in legacy/old browsers without ES6 support. So if your application requires supporting older browsers, var may still be a safer option over let.

Browser Support for Let/Const

{% chart %}

Browser Support Version
Chrome 49+
Firefox 44+
Safari 10+
IE/Edge 15+

{% endchart %}

Key Takeaways: To Let or Not Let

We‘ve covered a lot of ground exploring the intricacies of let vs var declarations. Here are some key takeaways:

  • Let enables reliable block-level scoping
  • Let variables are not hoisted, avoiding surprises
  • Let prevents duplicate declarations in same scope
  • Variables declared with let exist only within associated block
  • Var provides function-scoped variables hoisted to container function
  • Let is the standard method for declaring local variables in modern JS
  • Legacy browser support still requires var sometimes

Fundamentally, understanding scoping rules is essential for mastering JavaScript. Both let and var have applicable uses, but let eliminates bugs through strict block scoping.

So for clean and maintainable code, prefer let declarations whenever possible!