let vs const in JavaScript aka why don't you learn a bit of C?
Published: 2024-05-08 18:54:52

There's a raging war in the JavaScript community about when to use `let` or `const` and which one should not be used. Which is ridiculous. Some major JavaScript developers are blaming `const` as it’s not useful because you can still mutate the value in an object referenced by a `const` variable. Let’s get a deeper look on this and some background on from where we are coming from.

## var

First, there was `var`. Before ES6 you used `var` only for variable declarations. But it was sometimes hard to understand what’s happening, because of hoisting. Hoisting means that basically all of your variable declarations are moved to the top of your scope, declaration position is ignored. So you can define your variable later in code and use your variable before declaration. Mind blowing, right? 🤯

```jsx
x = 42;
console.log(x); // 42
var x;
```

The above example just works. Hoisting will move the `var x;` to the top and that’s how you can use the `x` variable anywhere in the scope. Although variable initialization is not working the same way, as only the variable declaration is hoisted, not the initialization. So the following will result in `Nan`.

```jsx
x = x + 1;
console.log(x); // NaN
var x = 0;
```

This behaviour is surely hard to understand cognitively and hard to maintain. So we moved over with ES6 to use `let` and `const`.

## let

The `let` keyword is easy to describe. You just need to forget about hoisting and it works the same way as `var` . You can re-assign any value to the variable at any point.

```jsx
x = 42; // ReferenceError: Cannot access 'x' before initialization
console.log(x);
let x;
```

Because of `let` variable declarations are no longer hoisted, the above code will not work and will throw an error when you’re trying to use `x` before initialization.

```jsx
let x = 0;
x = x + 1;
console.log(x); // 1
```

Here we re-assign a new value to `x`. Variable initialization set the value to `x` to be `0`. Then we mutated the value of `x` and so we got `1` as a result on the console. Let’s continue to `const`!

## const

ES6 also introduced the `const` keyword for variable declaration. This already seems to be off, as a constant is not a variable. But the definition of “variable” explains some parts already:

> "A variable in programming languages is a named storage location in computer memory that holds a data value. It is characterized by an identifier, which uniquely identifies it within the program. Variables can represent various types of data, such as integers, floating-point numbers, characters, or complex data structures. The value stored in a variable can change during the execution of the program, hence the term 'variable.' Programmers use variables to store and manipulate data, enabling dynamic behavior and flexibility in their programs.”

So variables are basically just identifiers to a slot in memory. When you use `var` or `let` the JavaScript runtime engine will let you change the value of that slot in memory. But when you use `const`, you are instructing the JavaScript runtime engine to prevent any change of the variable, so you can’t update the memory identified by that variable.

```jsx
const x = 0;
x = x + 1; // TypeError: Assignment to constant variable.
console.log(x);
```

You can only set the value to a `const` variable at initialization time. `const` variables are also not hoisted, the same way like `let` variables.

## The “problem” with const…

is that if your `const` variable is a reference type, like an object, array or function, you can’t change the value of the variable, but you can mutate the value it’s referencing to.

```jsx
const x = { a: 0 };
x.a = x.a + 1;
console.log(x.a); // 1
```

The above will perfectly work. But why? Is `const` not preventing mutation? It is! But only for the `x` variable. But why? Because `x` is a reference only to the object! With other maybe familiar words: it’s a pointer!

If we go back in time to the dark ages of programming, when developers used the “C” programming language to create awesome apps (most games of the ‘90s were developed by using C and Assembly), we can understand what’s happening here.

```jsx
#include <stdio.h>

int main() {
    int num = 5;
    const int *ptr = &num; // Declaring a constant pointer to an integer

    printf("Initial value: %d\n", *ptr); // Output: Initial value: 5

    // Attempting to modify the value through the const pointer
    //*ptr = 10; // This line will result in a compilation error

    // However, we can still modify the underlying value using a non-const pointer
    int *mutablePtr = (int *)ptr;
    *mutablePtr = 10;

    printf("Modified value: %d\n", *ptr); // Output: Modified value: 10

    return 0;
}
```

In C, you need to cast the const pointer to be able to change it, but as the const variable is only an identifier to a slot in memory, when you convince the compiler you are allowed to change the memory identified by the variable, well, you can do it! It’s just memory!

```jsx
#include <stdio.h>

// Define a struct
struct Point {
    int x;
    int y;
};

int main() {
    // Create an instance of struct Point
    struct Point point = {5, 10};

    // Declare a const pointer to a struct Point
    const struct Point *ptr = &point;

    printf("Initial values: (%d, %d)\n", ptr->x, ptr->y); // Output: Initial values: (5, 10)

    // Attempting to modify the values through the const pointer
    // ptr->x = 10; // This line will result in a compilation error

    // However, we can still modify the underlying values using a non-const pointer
    struct Point *mutablePtr = (struct Point *)ptr;
    mutablePtr->x = 10;
    mutablePtr->y = 20;

    printf("Modified values: (%d, %d)\n", ptr->x, ptr->y); // Output: Modified values: (10, 20)

    return 0;
}
```

This works for structs too, which is most close to what an object in JavaScript is. In JavaScript, your object represents a data structure in memory. As you are not specifying this object as non-mutable by using `Object.freeze` , you can mutate the values held in that object. Keep in mind, that nested objects are basically just pointers too and `Object.freeze` is only work for that single object you are freezing. For nested objects, you need to freeze every object used.

```jsx
const x = Object.freeze({
	a: {
		b: 0
	}
});
x.a.b = 42;
console.log(x.a.b); // 42
```

The above will just work, but when you change the code to this:

```jsx
const x = Object.freeze({
	a: Object.freeze({
		b: 0
	})
});
x.a.b = 42;
console.log(x.a.b); // 0
```

You will get back the same value as before, `x.a.b` will be `0`. I admit, that this is still not the same as using a primitive `const` variable as the above will not throw an error, it just silently doesn't change the value of `x.a.b`.

So in case of a JavaScript `const` variable, you can’t change the pointer / reference, but you change the underlying memory. It is a false assumption, that the `const` specifier will affect the referenced object too. Why would it?

## Cognitive load

What’s the role of `let` when you want immutability all the time? Marking and flagging all involved parties that you want to change the value of the variable at some point in your code! This doesn’t mean that it has to be another developer, it can be a static analysis tool or AI, anyone or anything that can understand code.

During code reviews `let` will help the reviewer to take special care about handling the variable. When I encounter such a case I instantly switch into questioning the authors code, why there’s a `let` variable. Is there any reason to use `let`? At any point is the value mutated? Why is it mutated?

## Conclusion

As every language feature, `let` and `const` are just tools the developer can use. No one is forced to use `const`. It is not necessary to do so. But it provides values and by not using it, you miss those values.

What’s the value in saving 2 characters? Time? Code completion and any type of artificial help in typing in the variable definition makes this obsolete. Size of the bundle used? 2kb, maybe 20k in case of a very large library? While we are watching billions of videos online?

It would be better to review over-engineered and unnecessary architectures or tech stacks we use, than complaining about using `const`.