The let keyword in JavaScript is like the old var keyword in JavaScript except that it respects block scope. Before ES6, JavaScript only had function scope. Although there is nothing fundamentally wrong with function scope, it confuses a lot of people coming from C based languages which have block scope.

1) Block scope vs Function scope

Scope refers to some portion of the code where a variable exists. Outside of that scope the variable will be not be accessible or will be another variable with the same name.

a) Function scope

Function scope means that any variable defined anywhere inside a function, will be accessible anywhere in that function and will not exist outside of it.

b) Block scope

Block scope means that any variable defined anywhere inside any block (for example if and for blocks), will be accessible anywhere inside that block and will not exist outside of it.

2) JavaScript var and let

In JavaScript, the var keyword is function scoped. The let keyword is block scoped.

a) Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function main() {

var a = 'function-a';
var b = 'function-b';

console.log(a); // 'function-a'
console.log(b); // 'function-b'
console.log(c); // ReferenceError Exception: c is not defined
console.log(d === undefined); // true

if(a !== b) {

let a = 'block-a'; // Sets the value of a but only in the if block
var b = 'block-b'; // Sets the value of b in the entire main() function
let c = 'block-c'; // Defined only in the if block
var d = 'block-d'; // Defined in the entire main() function

console.log(a); // 'block-a'
console.log(b); // 'block-b'
console.log(c); // 'block-c'
console.log(d); // 'block-d'
}

console.log(a); // 'function-a'
console.log(b); // 'block-b'
console.log(c); // ReferenceError Exception: c is not defined
console.log(d); // 'block-d'
}

b) Notes

  • Variables defined with var inside an if block will exist inside the entire function they are defined in – ignoring the if block.
  • Variables defined with var will exist and have a value of undefined in the section of the code before the initialisation. Check out variable d in the example. The variable is created at the beginning of the function, this is called variable hoisting.
  • Variables defined with let will only exist within the block they are defined in. Trying to access the variable outside of the block will throw an Error. This is the case for c in the example.
  • Use let to override the value of a function scoped variable inside a block only. Example: a.

3) When to use let

a) In for loops

Do

1
2
3
for(let i; i < n; i = i + 1) {
[...]
}

instead of

1
2
3
4
var i; // Will be function scoped and can cause interferences
for(i = 0; i < n; i = i + 1) {
[...]
}

b) In closures that need to retain creation time values

1
2
3
4
5
6
7
8
for(var i = 1; i < 4; i = i + 1) {

let j = i;
setTimeout(function() {
console.log(i); // Will always print out 4
console.log(j); // Will print out 1, 2 and 3 as expected
}, 500)
}

Useful to create multiple instances of a variable inside a block.

c) For temporary variables inside blocks

1
2
3
4
5
6
var i = 0;
while(i < 75) {
let random = Math.random();
i = random * 100;
console.log(i);
}

4) let caveats and warnings

a) No access to the variable before declaration

1
2
3
4
5
6
7
8
function main() {

console.log(foo); // Will throw an error
console.log(bar); // Prints nothing as bar is undefined but will not throw an error

let foo = 'foo';
var bar = 'bar';
}

At first glance, the let keyword does behave the same as var when used in a function block. But if you try to access the variable before declaration you will get an error instead of an undefined variable as you would with var.

b) Double declarations are forbidden

1
2
3
4
if(condition) {
let amount = 0;
let amount = 1; // will throw an Error
}

c) Be careful when you feel like replacing a var with let

Be careful when you feel like changing var with let. Usually that happens when a function should be split into several parts.

If your initial function looks like

1
2
3
4
5
6
7
function process(callback) {

var result = 'foo';

if(dayOfTheWeek = 'Monday') {
callback(result);
}

then suddenly looks like

1
2
3
4
5
6
7
8
9
10
11
12
function process(callback) {

if(dayOfTheWeek = 'Monday') {
let result = 'foo';
callback(result);
}

if(isRaining) {
let result = 'bar';
callback(result);
}
}

You should seriously consider rewriting your function as two functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function process1(callback) {

var result = 'foo';

if(dayOfTheWeek = 'Monday') {
callback(result);
}
}

function process2(callback) {

var result = 'bar';

if(isRaining) {
callback(result);
}
}

Read more about why functions should only do one thing