Foundational JavaScript Concepts: Variable Assignment and Primitive/Object Mutability
Tags: javascript, webdev, programming, tutorials
Published: 2020-03-22 12:41:59

---
title: Foundational JavaScript Concepts: Variable Assignment and Primitive/Object Mutability
published: true
description: If you're not familiar with how JavaScript variable assignment and primitive/object mutability works, you might find yourself encountering bugs that you can't quite explain
tags: javascript, webdev, programming, tutorials
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/n87ikxuexxvlpxf7u1zj.png
---

If you're not familiar with how JavaScript variable assignment and primitive/object mutability works, you might find yourself encountering bugs that you can't quite explain. **I think this is one of the more important foundational JavaScript topics to understand**, and I'm excited to share it with you today!

# JavaScript Data Types

JavaScript has seven _primitive_ data types[1]:

- Boolean (`true`, `false`)
- Null (`null`)
- Undefined (`undefined`)
- Number (e.g., `42`)
- BigInt (e.g., `10000000000000000n`)
- String (e.g., `"Hello world"`)
- Symbol (e.g., `Symbol(11)`)

Additionally, JavaScript has _object_ data types. JavaScript has several built-in object data types, the most well-known and widely-used being `Array`, `Object`, and `Function`.

# Assignment, Reassignment, and Mutation

Assignment, reassignment, and mutation are important concepts to know and differentiate in JavaScript. Let's define each and explore some examples.

## Assignment

To understand assignment, let's analyze a simple example.

```javascript
let name = 'Julie';
```

To understand what happened here, we need to go right-to-left:

1. We create the string `"Julie"`
2. We create the variable `name`
3. We _assign_ the variable `name` a reference to the string we previously created

So, assignment can be thought of as the process of creating a variable name and having that variable refer to data (be it a primitive or object data type).

## Reassignment

Let's extend the last example. First, we will assign the variable `name` a reference to the string `"Julie"` and then we will _reassign_ that variable a reference to the string `"Jack"`:

```javascript
let name = 'Julie';
name = 'Jack';
```

Again, the play-by-play:

1. We create the string `"Julie"`
2. We create the variable `name`
3. We _assign_ the variable `name` a reference to the string we previously created
4. We create the string `"Jack"`
5. We _reassign_ the variable `name` a reference to the string `"Jack"`

If this all seems basic, that's okay! We're laying the foundation for understanding some more complicated behavior and I think you'll be glad we did this review.

## Mutation

Mutation is the act of _changing_ data. It's important to note that, in our examples thus far, we haven't _changed_ any of our data.

### Primitive Mutation (spoiler: you can't)

In fact, we _wouldn't_ have been able to change any of our data in the previous example even if we wanted to&mdash;primitives can't be mutated (they are _immutable_). Let's try to mutate a string and bask in the failure:

```javascript
let name = 'Jack';
name[2] = 'e';
console.log(name);
// "Jack"
```

Obviously, our attempt at mutation failed. This is expected: we simply can't mutate primitive data types.

### Object Mutation

We absolutely can mutate objects! Let's look at an example.

```javascript
let person = {
  name: 'Beck',
};
person.name = 'Bailey';
console.log(person);
// { name: "Bailey" }
```

So yeah, that worked. It's important to keep in mind that we never _reassigned_ the `person` variable, but we did _mutate_ the object at which it was pointing.

# Why This All Matters

**Get ready for the payoff.** I'm going to give you two examples mixing concepts of assignment and mutation.

## Example 1: Primitives

```javascript
let name = 'Mindy';
let name2 = name;
name2 = 'Mork';
console.log(name, name2);
// "Mindy" "Mork"
```

Not very surprising. To be thorough, let's recap the last snippet in more detail:

1. We create the string `"Mindy"`
2. We create the variable `name` and assign it a reference to the string `"Mindy"`
3. We create the variable `name2` and assign a reference to the string `"Mindy"`
4. We create the string `"Mork"` and _reassign_ `name2` to reference that string
5. When we `console.log` `name` and `name2`, we find that `name` is still referencing `"Mindy"` and `name2` is referencing the string `"Mork"`

## Example 2: Objects

```javascript
let person = { name: 'Jack' };
let person2 = person;
person2.name = 'Jill';
console.log(person, person2);
// { name: "Jill" }
// { name: "Jill" }
```

If this surprises you, try it out in the console or your favorite JS runtime environment!

Why does this happen? Let's do the play-by-play:

1. We create the object `{ name: "Jack" }`
2. We create the `person` variable and assign it a reference to the created object
3. We create the `person2` variable and set it equal to `person`, which is referring to the previously-created object. (Note: `person2` is now referencing the same object that `person` is referencing!)
4. We create the string `"Jill"` and _mutate_ the object by _reassiging_ the `name` property to reference `"Jill"`
5. When we `console.log` `person` and `person2`, we note that the one object in memory that both variables were referencing has been mutated.

Pretty cool, right? And by cool, I mean potentially scary if you didn't know about this behavior.

# The Real Differentiator: Mutability

As we discussed earlier, primitive data types are immutable. That means we really don't have to worry about whether two variables point to the same primitive in memory: that primitive won't change. At best, we can _reassign_ one of our variables to point at some other data, but that won't affect the other variable.

Objects, on the other hand, are mutable. Therefore, we have to be keep in mind that multiple variables may be pointing to the same object in memory. "Mutating" one of those variables is a misnomer, you're mutating the object it's referencing, which will be reflected in any other variable referencing that same object.

# Is This a Bad Thing?

This question is far too nuanced to give a simple yes or no answer. Since I have spent a good amount of time understanding JavaScript object references and mutability, I feel like I actually use it to my advantage quite a bit and, _for me_, it's a good thing. But for newcomers and those who haven't had the time to _really_ understand this behavior, it can cause some pretty insidious bugs.

# How Do I Prevent This from Happening?

In many situations, you don't want two variables referencing the same object. The best way to prevent this is by _creating a copy_ of the object when you do the assignment.

There are a couple ways to create a copy of an object: using the _Object.assign_ method and _spread operator_, respectively.

```javascript
let person = { name: 'Jack' };
// Object.assign
let person2 = Object.assign({}, person);
// Spread operator
let person3 = { ...person };
person2.name = 'Pete';
person3.name = 'Betty';
console.log(person, person2, person3);
// { name: "Jack" }
// { name: "Pete" }
// { name: "Betty" }
```

Success! **But a word of caution:** this isn't a silver bullet because we're only creating _shallow copies_ of the person object.

## Shallow Copies?

If our object has objects nested within it, shallow copy mechanisms like _Object.assign_ and the _spread operator_ will only create copies of the root level object, but deeper objects will still be shared. Here's an example:

```javascript
let person = {
  name: 'Jack',
  animal: {
    type: 'Dog',
    name: 'Daffodil',
  },
};
person2 = { ...person };
person2.name = 'Betty';
person2.animal.type = 'Cat';
person2.animal.name = 'Whiskers';
console.log(person);
/*
{
  name: "Jack",
  animal: {
    type: "Cat",
    name: "Whiskers"
  }
}
*/
```

Ack! So we copies the top level properties but we're still sharing references to deeper objects in the object tree. If those deeper objects are mutated, it's reflected when we access either the `person` or `person2` variable.

## Deep Copying

Deep copying to the rescue! There are a number of ways to deep copy a JavaScript object[2]. I'll cover two here: using _JSON.stringify/JSON.parse_ and using a _deep clone_ library.

### JSON.stringify/JSON.parse

If your object is simple enough, you can use `JSON.stringify` to convert it to a string and then `JSON.parse` to convert it back into a JavaScript object.

```javascript
let person = {
  name: 'Jack',
  animal: {
    type: 'Dog',
    name: 'Daffodil',
  },
};
person2 = JSON.parse(JSON.stringify(person));
```

And this will work... but only in limited situations. If your object has any data that cannot be represented in a JSON string (e.g., functions), that data will be lost! A risky gambit if you're not super confident in the simplicity of your object.

### Deep Clone Library

There are a lot of good deep clone libraries out there. One such example is lodash with its `_.cloneDeep` method. These libraries will generally traverse your object and do shallow copies all the way down until everything has been copied. From your perspective, all you have to do is import lodash and use `cloneDeep`:

```javascript
let person = {
  name: 'Jack',
  animal: {
    type: 'Dog',
    name: 'Daffodil',
  },
};
person2 = _.cloneDeep(person);
```

# Conclusion

This discussion is really the tip of the iceburg when it comes to variable assignment and data mutability in JavaScript. I invite you to continue researching this topic, experimenting with topics like equality comparison when assigning object references and copying objects.

---

References:

1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
2. https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript