No matter how many times I revisit it, I have pretty consistent track record of being tripped up by how JavaScript assigns values to variables.

Primitives: Assigned by Value

Primitive values (numbers, strings, etc.) are assigned by value, meaning that assigning one variable to another variable assigned to a primitive value will result in two, distinct values being stored in memory. The entire value is copied.

const number1 = 100; 
const number2 = number1; 

// Variable / Memory Value: 
// number1 -> 100
// number2 -> 100

Objects: Assigned by Reference

Everything else (objects, including functions, arrays, etc.) are assigned by reference. Assigning a newly created object to a variable is actually creating a reference to that object’s location in memory. Any further variable assignments will also point to that exact same location.

const object1 = { someProperty: 'some value' };
const object2 = object1;

// Variable / Memory Value: 
// object1 -> { someProperty: 'some value' }
// object2 -> object1

And that’s why things like this work. If you mess with the properties of an object — no matter which variable is referencing it — that central value in memory will be changed, impacting every variable pointing to it.

let object1 = { someProperty: 'some value' };
let object2 = object1;
let object3 = object2;
let object4 = object3;
let object5 = object4;

object5.someProperty = 'some OTHER value!';

console.log(object1);
// { someProperty: 'some OTHER value!' }

A Map() Follows the Same Rules

This is a pretty fundamental concept in JavaScript, but that didn’t stop me from forgetting about it while dealing with a Map(). Just like a regular, old variable, a value inside a Map() is stored differently depending on the value’s type — primitive or otherwise. From MDN:

If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map object.

In my case, I was working with somthing like this (highly contrived):

const aMap = new Map();
const anObj = {
  name: 'Alex'
}

aMap.set('a', anObj);
aMap.set('b', anObj);
aMap.set('c', anObj);

aMap.get('c').name = 'Bob';

console.table(Array.from(aMap.entries()));

While I was intending to modify the name only by on the a key, the results prints as follows:

| (index) |  0  |        1        |
-----------------------------------
|    0    | 'a' | { name: 'Bob' } |
|    1    | 'b' | { name: 'Bob' } |
|    2    | 'c' | { name: 'Bob' } |

Bob is everywhere. In hindsight, this is no surprise. Primitives by value, objects by reference. If I want these values to be updated independently, the answer is to duplicate the object, reserving it a new, distinct place in memory:

const aMap = new Map();
const anObj = {
  name: 'Alex'
}

- aMap.set('a', anObj);
+ aMap.set('a', {...anObj});
- aMap.set('b', anObj);
+ aMap.set('b', {...anObj});
- aMap.set('c', anObj);
+ aMap.set('c', {...anObj});

aMap.get('c').name = 'Bob';

console.table(Array.from(aMap.entries()));

And with that, you’re released to update properties freely:

| (index) |  0  |        1         |
------------------------------------
|    0    | 'a' | { name: 'Alex' }  |
|    1    | 'b' | { name: 'Alex' } |
|    2    | 'c' | { name: 'Bob' } |

Just one “Bob,” as desired.

Only a Matter of Time

Stay tuned for another post basically covering the same concept within another context after it inevitably trips me up again.