Why the VAR and LET keywords shouldn't be used interchangeably
Tags: javascript, keywords, variables
Published: 2022-01-13 21:07:19

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:

```js
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)
```

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](https://www.youtube.com/watch?v=SBwoFkRjZvE) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gh6761ii4to8u3xbc2rj.png)


## 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](https://www.youtube.com/watch?v=8aGhZQkoFbQ) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6zdr8c1jcrvpciqs6kwb.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q262ljwth6nzced54uhp.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vualo2hrxz5vg91epwf4.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k11x9spk1jreg619ipx8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1scohct2nmjh4wdq0brl.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jsemi3zyg0io6u178nty.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5njmwyabuqxr7ly2of7s.png)

**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:

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

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