DEV Community

Cover image for Why the VAR and LET keywords shouldn't be used interchangeably
Richard Oliver Bray
Richard Oliver Bray

Posted on

Why the VAR and LET keywords shouldn't be used interchangeably

If you'd asked me about the var and let Javascript keywords about a week ago I would say for the most part, they are interchangeable. Yes var is function scoped and let is block scoped but I really ran into a situation where switching them around caused any issues. Until recently...

A friend of mine show me this piece of code:

function fn() {
  for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1000 * i);
  }
}
fn();
// >>>>> Output <<<<
// 3 (immediately)
// 3 (after one second)
// 3 (after two seconds)

---

function fn() {
  for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1000 * i);
  }
}
fn();

// >>>>> Output <<<<
// 0 (immediately)
// 1 (after one second)
// 2 (after two seconds)
Enter fullscreen mode Exit fullscreen mode

I assumed it would loop though 0-2 with both keywords but I was shocked to see it only did that when let was used and not var. At the time I had no idea why that was the case so I did some research and thought I'd put my findings in a post for others to learn from.

To fully understand why this happens we need to talk about two things; scope and the event loop.

1. Scope

The var keyword has function level scope and the let keyword has block level scope, but what does that actually mean?

Scope in most programming languages refers to the access functions and objects have to variables depending on where they are placed. There is a great video by LeanCode.academy that explains the details of what scope is, but for the code I shared above, when the var keyword is used, it can be read and reassigned outside of the loop within the fn function. When let is used, it cannot be read or reassigned outside of the loop even within the function.

the difference between block and function scope

2. The event loop

Javascript is a single-thread language, I won't go into detail on what that means in this post, all you need to know is that it can't execute multiple things at the same time. However, with the power of the event loop Javascript can give the illusion executing multiple things by putting some code aside and coming back to it later, when this happens really fast it sort of feels like multiple things are running at the same time. This is how the setTimeout function works.

There's a great video by Philip Roberts that explains in detail how the event loop works but for this post I'm going to simplify it a bit for our case.

Javascript code in the browser is placed in the call stack before being executed. If the code needs to be executed later (if it's a promise or setTimeout etc...) it is handled by some browser APIs (which we won't talk about in detail), before going into the task or event queue. The event loop is constantly checking if the call stack is empty, if it is, it will add an event from the event queue to the call stack to be executed. Let's go through our code with the event loop in mind.

a. When our code first runs it adds our main function (fn), then the loop to the call stack. This happens for both let and var versions:

main function and loop added to call stack

b. The first setTimeout is reached then added to the call stack, but then moved to the browser APIs to be handled later.

first setTimeout moved to browser API

c. The first setTimeout has an instruction to execute after 0 milliseconds (1000 * i where i is 0 on the first loop). So that immediately get's sent to the event loop and will stay there until the call stack is empty.

first setTimeout gets sent to event queue

d. The loop will run two more times and move the following setTimeout functions from the call stack to browser APIs with instructions to execute after 1000 and 2000 milliseconds respectively.

all setTimeouts added to browser API column

e. After that's done the loop and the main function are popped off the call stack, the event loop checks if there's anything in the event queue and since there is, that get's moved to the call stack.

call stack is empty and first setTimeout move to it because of event loop

f. At this point the first setTimeout function will run and i will be set at the time this executes.

Bear in mind that the loop has already run three times at this point, so when var is used by the time this executes i is equal to 3, why? The first time the loop runs i is 0, then 1, then 2. Because var is function scoped it will increment from 2 to 3 and then not run the loop.

In the case of let however, because it is block scoped, even after the loop has run three times when this setTimeout executes, the value of i is 0, it hasn't been reassigned.

g. The next setTimeout function moves to the event queue after 1000 milliseconds then with the help of the event loop quickly moves into the empty call stack and executes. Again at this stage if the var keyword is used i will equal 3, but for let when the console log runs it looks for the block scoped value of i is 1, this was retained when this event was first moved from the call stack to the browser api so that will print 1 to the console.

The remaining setTimeout functions moving through the event loop cycle

You've probably figured out what happens to the last setTimeout function so I won't go through that.


If you add a breakpoint to the function in the setTimeout and look in the Chrome dev tools the difference between function and block scoping is a tiny bit more obvious.

different between let and var in Chrome dev tools

var shows the function scoped variable in the main function (fn), but let shows the loop (_loop_1) inside the main function which is the extra bit of information saved in the event that goes through the event loop remembering the block scoped value of the variable.

Final words

I know this post is quite long but I hope you found it useful. Whenever you can, use let instead of var . If you can't, there ware ways of getting block scoping with var but it results in some weird looking code:

function fn() {
  for (var i = 0; i < 3; i++) {
    ((i) => setTimeout(() => console.log(i), 1000 * i))(i);
  }
}
fn();
Enter fullscreen mode Exit fullscreen mode

Happy coding 🧑🏿‍💻

Sources

https://www.outsystems.com/blog/posts/asynchronous-vs-synchronous-programming/
https://www.educative.io/edpresso/what-is-an-event-loop-in-javascript
https://stackoverflow.com/questions/31285911/why-let-and-var-bindings-behave-differently-using-settimeout-function

Top comments (0)