let and const in javascript

mohananagavenkat profile image Mohana Naga Venkat Sayempu ・3 min read

Note: This is part of my es6 series of posts.

The let keyword declares a variable that is strictly scoped to the current block, statement, or expression where it is defined. This is in contrast to var declarations which are scoped to the current function. An additional difference with var is that let variables are not hoisted to the top of the scope and they can only be used at a point in the code after they have been defined.

const variables share all these characteristics with the additional restriction that redeclaring them will generate an error, and changing their value once declared will fail silently.

As a rule of thumb const provides the strictest usage contract and clearly signals a narrow intent that the variable will not be redeclared or subsequently have its value reassigned, therefore const should be used in preference to let and var wherever appropriate.

Examples of "blocks" that let and const variables are scoped to include if statements, for loop headers and bodies and naked {} blocks.

Block Scope

Attempting to access a let or const variable outside the block it's declared in will throw an error. Note the use of a naked {} block in this example to arbitrarily create a new scope.

var a = 1;

  let b = 2;

console.log(a); // 1
console.log(b); // ReferenceError, b is undefined


Unlike var declarations which are hoisted to the top of their enclosing scope let and const declarations may only be accessed after they've been declared. let and const variables are said to be in the scope's TZD (temporal dead zone) before they've been declared, and any attempt to read or write them beforehand will generate an error.

⚠️ Most transpilers currently don't handle this behavior fully to-spec, so the above example will probably only error in a native ES6 environment.

  console.log(foo); // undefined
  console.log(bar); // ReferenceError: bar is in the 'TDZ'
  var foo = 'foo';
  let bar = 'bar';

Loop Scope

When let is used in a for loop header a new i is scoped for each iteration of the loop. This makes writing async code in loops more intuitive since the closure doesn't need to be created manually. This can also help with traditionally counter-intuitive tasks such as applying click event handlers in a loop.

for (var i=1; i<=5; i++) {
    }, i*100);
// 6,6,6,6,6

for (let i=1; i<=5; i++) {
    }, i*100);
// 1,2,3,4,5

Implicit Scope Creation

Using let within an if block implicitly creates a new scope. This is a hazard of using let. The new scope is easily spotted in the simple example above, but when code becomes more complicated hunting for new scopes created by let could become a cognitive burden. A rule of thumb is to place let declarations at the top of their enclosing block to clearly signpost their use and also avoid being bitten by the TDZ.

if ( foo ) {
    // We're in the same scope as outside the 'if'

if ( foo ) {
    // We're in a new scope
    let a = 1;

Read-Only const

As mentioned, reassign a value to a constant will silently fail while redeclaring the constant will throw an error.

const foo = 'foo';
foo = 'bar' // Silently fails, foo is still equal to 'foo'
const foo = 'bar' // TypeError, foo has already been defined

However constants are not immutable, therefore the properties of non-primitive values defined as a constant may be manipulated freely.

const foo = {a: 1};
foo.a = 2;
foo.a; // 2

Happy coding 😃.


Editor guide