JavaScript & Memory
Last updated
Was this helpful?
Last updated
Was this helpful?
As programmers, declaring , initializing them (or not), and assigning them new values later on is something we do on a daily basis.
But what actually happens when do this? How does JavaScript in particular handle such basic functionality internally? And more importantly, how does it benefit us as programmers to understand the underlying minutiae of JavaScript?
Letβs start off with a simple example. Below, we declare a variable called myNumber and initialize it with a value of 23.
When this code is executed, JS willβ¦
Create a unique identifier for your variable (βmyNumberβ).
Allocate an address in (will be assigned at runtime).
Store a value at the address allocated (23).
If we were to create a new variable called βnewVarβ and assign it βmyNumberββ¦
β¦ since myNumber technically equals β0012CCGWH80β, newVar would also equal β0012CCGWH80β, which is the memory address that holds the value 23. Ultimately, this has the intended effect of colloquially saying, βnewVar is now equal to 23.β
Since myNumber equals the memory address β0012CCGWH80β, assigning it to newVar assigns β0012CCGWH80β to newVar.
Now, what happens if I do this:
βmyNumberβ will surely have the value of 24. But will newVar also have the value of 24 since they point to the same memory address?
The answer is no. Since primitive data types in JS are immutable, when βmyNumber + 1β resolves to β24β, JS will allocate a new address in memory, store 24 as its value, and βmyNumberβ will point to the new address.
Hereβs another example:
While a novice JS programmer may say that the letter βdβ is simply appended to the string βabcβ wherever it exists in memory, this is technically false. When βabcβ is concatenated with βdβ, since strings are also primitive data types in JS, a new memory address is allocated, βabcdβ is stored there, and βmyStringβ points to this new memory address.
The next step is to understand where this memory allocation is happening for primitives.
For the purposes of this blog, the JS memory model can be understood as having two distinct areas: the call stack and the heap.
The call stack is where primitives are stored (in addition to function calls). A rough representation of the call stack after declaring the variables in the previous section is below.
In the illustrations that follow, Iβve abstracted away the memory addresses to show the values of each variable. However, donβt forget that in actuality the variable points to a memory address, which then holds a value. This will be key in understanding the section on let vs. const.
The heap is where non-primitives are stored. The key difference is that the heap can store unordered data that can grow dynamicallyβperfect for arrays and objects.
Non-primitive JS data types behave differently compared to primitive JS data types.
Letβs start off with a simple example. Below, we declare a variable called βmyArrayβ and initialize it with an empty array.
When you declare a variable βmyArrayβ and assign it a non-primitive data type like β[]β, this is what happens in memory:
Create a unique identifier for your variable (βmyArrayβ).
Allocate an address in memory (will be assigned at runtime).
Store a value of a memory address allocated on the heap (will be assigned at runtime).
The memory address on the heap stores the value assigned (the empty array []).
From here, we could push, pop, or do whatever we wanted to to our array.
In general, we should be using const as much as possible and only using let when we know a variable will change.
Letβs be really clear about what we mean by βchangeβ.
A mistake is to interpret βchangeβ as a change in value. A JS programmer who interprets βchangeβ this way will do something like this:
This programmer correctly declared βsumβ using let, because they knew that the value would change. However, this programmer incorrectly declared βnumbersβ using let, because they interpret pushing things onto the array as changing its value.
The correct way to interpret βchangeβ is a change in memory address. Let allows you to change memory addresses. Const does not allow you to change memory addresses.
Letβs visualize whatβs happening here.
When βimportantIDβ is declared, a memory address is allocated, and the value of 489 is stored. Remember to think of the variable βimportantIDβ as equalling the memory address.
When 100 is assigned to βimportantIDβ, since 100 is a primitive, a new memory address is allocated, and the value of 100 is stored there. Then JS tries to assign the new memory address to βimportantIDβ, and this is where the error is thrown. This is the behavior we want, since we donβt want to change the ID of this very important IDβ¦
When you assign 100 to importantID, youβre actually trying to assign the new memory address where 100 is stored. This is not allowed since importantID was declared with const.
As mentioned above, the hypothetical novice JS programmer incorrectly declared their array using let. Instead, they should have declared it with const. This may seem confusing at the outset. Itβs not at all intuitive, I admit. A beginner would think that the array is only useful to us if we can change it, and const makes the array unchangeable, so why use it? However, remember: βchangeβ is defined by the memory address. Letβs take a deeper dive on why itβs totally okay and preferred to declare the array using const.
When myArray is declared, a memory address is allocated on the call stack, and the value is a memory address that is allocated on the heap. The value stored on the heap is the actual empty array. Visualized, it looks like this:
If we were to do thisβ¦
β¦ this pushes numbers onto the array that exists in the heap. However, the memory address of βmyArrayβ has not changed. This is why although βmyArrayβ was declared with const, no error is thrown. βmyArrayβ still equals β0458AFCZX91β, which has a value of another memory address β22VVCX011β, which has a value of the array on the heap.
An error would be thrown if we did something like this:
Since 3 is a primitive, a memory address on the call stack would be allocated, and a value of 3 would be stored, then we would try to assign the new memory address to myArray. But since myArray was declared with const, this is not allowed.
Another example that would throw an error:
Since [βaβ] is a new non-primitive array, a new memory address on the call stack would be allocated, and a value of a memory address on the heap would be stored, the value stored at the heap memory address would be [βaβ]. Then we would try to assign the call stack memory address to myArray, and this would throw an error.
For objects declared with const, like arrays, since objects are non-primitive, you can add keys, update values, so on and so forth.
While we would colloquially say, βmyNumber equals 23β, more technically, myNumber equals the that holds the value 23. This is a crucial distinction to understand.
Now, the .
JavaScript is the #1 programming language in the world (according to GitHub and Annual Developer Survey). Developing a mastery and becoming a βJS Ninjaβ is what we all aspire to be. Any decent JS course or book advocates for const and let over var, but they donβt necessarily say why. It is unintuitive for beginners why certain const variables throw an error upon βchangingβ its value while others do not. It makes sense to me why these programmers then default to using let everywhere to avoid the hassle.