A "Gotcha" of JavaScript's Pass-by-Reference
Tags: javascript, typescript, functional, codequality
Published: 2020-08-24 00:56:45

A few days ago, I posted an article talking about Pass By Reference Trolls.  Specifically, I was calling out those people who stubbornly refuse to acknowledge JavaScript's _native_ pass-by-reference behavior, despite any proof you might show them.  (If you're so inclined, you can read the article here: https://dev.to/bytebodger/troll-hunting-101-javascript-passes-objects-by-reference-40c8)

Because I've grown so weary of the Pass By Reference Trolls, I put a note at the bottom of that article explaining that, in contrast to my normal behavior, I would **_not_** be replying directly to any comments.  However, after reading a few replies, I realized that there is a critical area of ambiguity on this matter that probably fosters a good portion of the confusion.

Rather than reply to those commenters directly (which I swore I would not do), I realized that this might be a prime opportunity for a follow-on article.  To be absolutely clear, the particular people who replied to my last article were **_not_** acting trollish in any way.  They were presenting respectful and well-reasoned counter-points, which is why I thought it might be best just to clarify things in this article.

In fact, the more I thought about this over the weekend, the more I realized that this is kinda like the Monty Hall Problem.  (If you're not well familiar with it, google it.  No, seriously.  Right now.  Go google it.  It's _fascinating_.)
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/p1arkvjqu8zjzcpr73d9.png)
## JavaScript's Monty Hall Problem
I won't bore you with a thorough recap of the Monty Hall Problem.  (But have I mentioned that, if you don't know about it, you should google it??)  

The key thing that interests me about it is that, on one level, it's actually an extremely _simple_ problem.  There's no calculus.  No advanced concepts of theoretical physics.  No quantum mechanics.  It's a very _basic_ puzzle of _elementary_ probabilities.  And yet, if people haven't already been exposed to the problem, the vast majority will come to the absolutely **_wrong_** solution.  

But it gets more interesting than that.  There's something about the way that the problem is received in the brain that causes even advanced academics to become _extremely defensive and/or combative about defending their erroneous solution_.

Seriously.  Learn about the Monty Hall Problem.  Then find someone who's not familiar with it - preferably someone with advanced academic or professional status.  Then, when they give you the _wrong_ solution, watch as they protest, _vehemently_, about how mistaken they believe you are.

In this regard, pass-by-reference is strikingly similar to the Monty Hall Problem.  Once someone gets it in their head that "JavaScript has no pass-by-reference!!!" it becomes nearly impossible to dislodge their erroneous conclusion.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vtdqe5gj0ir6hyohhbij.jpg)
## The Setup
If you've been writing code for, oh... five minutes or so, nothing in this next example will surprise you.  Nevertheless, it's important to illustrate the _extremely simple_ concept at play:

```javascript
// initialize our variables
let mostImportantNumber = 3.14;
let spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };

// use these variables to initialize some NEW variables
let answerToEverything = mostImportantNumber;
let germanNumbers = spanishNumbers;

// mutate the NEW variables to our liking
answerToEverything = 42;
germanNumbers.one = 'einz';
germanNumbers.two = 'zwei';
germanNumbers.three = 'drei';

// inspect the ORIGINAL variables
console.log(mostImportantNumber);  
// 3.14 - no surprise here
console.log(spanishNumbers); 
// {one: 'einz', two: 'zwei', three: 'drei'}
// wait a minute... that doesn't look like Spanish
```

As I discussed in my previous article, there are some people who want to dive into pedantics with the argument that, "That's not _passing_ by reference!  _Passing_ requires a function!"

Umm... no.  It doesn't.  You can "pass" a value into a new variable by using the value to initialize the variable.  But even if we give in to the Passing Police, we can write this with a function and the effect is no different.  

```javascript
const mutate = (aNumber, numberNames) => {
  aNumber = 42;
  numberNames.one = 'einz';
  numberNames.two = 'zwei';
  numberNames.three = 'drei';
}

// initialize our variables
let mostImportantNumber = 3.14;
let spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };

// use these variables to initialize some NEW variables
let answerToEverything = mostImportantNumber;
let germanNumbers = spanishNumbers;

// mutate the NEW variables to our liking
mutate(mostImportantNumber, spanishNumbers);

// inspect the ORIGINAL variables
console.log(mostImportantNumber);  
// 3.14 - no surprise here
console.log(spanishNumbers); 
// {one: 'einz', two: 'zwei', three: 'drei'}
// wait a minute... that doesn't look like Spanish
```

Strangely enough, I've never had anyone argue with me that the primitive value (`mostImportantNumber`) and the object (`spanishNumbers`) are treated _the same_.  It's pretty clear to the naked eye that _something_ different is happening with these variables.  But I've still had multiple Language Nerds stubbornly tell me that _both_ variables are passed by value - even though they are obviously processed at runtime in very different ways.

But as I mentioned above, there were two commenters on my previous article who gave interesting "gotcha" examples.  And the more I thought about it, the more I became convinced that it may be examples _like those_ that are causing some people confusion.

So let's explore the "counter examples" they gave...
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8yymv5i4l5wgbgsgtx3a.jpg)
## Fumbled References
@iquardt gave this basic-yet-vexing example:

```javascript
const foo = xs => {
  xs = [1];
};

let xs = [];
foo(xs);
console.log(xs);  // []
```

On the surface, this seems to "break" my pass-by-reference position, right?  I mean, the `xs` array is passed into `foo()`, where it's mutated.  But the _original_ `xs` array is unaltered.  So... no pass-by-reference??

Well, let's dive a bit deeper.  

First, the example is a bit convoluted because we have the same variable name outside and inside the function scope.  This always makes it a bit harder to follow what's actually happening as we try to mentally organize the scopes.  So I'll make it a _little_ clearer by renaming the variable in the function signature.

```javascript
const foo = someArray => {
  xs = [1];
};

let xs = [];
foo(xs);
console.log(xs);  // [1]
```

This seems to "work" - but it has nothing to do with pass-by-value or pass-by-reference.  It has to do with the fact that, inside the function, there is no definition for `xs`.  But in JS, functions have access to variables in their calling scope.  JS looks outside the function scope and finds a definition for `xs` and updates it accordingly.

To get a clearer view on pass-by-value/reference, let's complete the de-obfuscation of `foo()` by changing the name of the _inner_ variable and also tweaking the outer one.

```javascript
const foo = someArray => {
  someArray = [1];
};

let originalArray = [];
foo(originalArray);
console.log(originalArray);  // []
```

This is functionally equivalent to @iquardt's first example.  And just like in that example, the outer-scoped array of `originalArray` remains unchanged.  This is where people claim that JS objects are not passed by reference.  The thinking goes like this:

If objects (and arrays are objects) are passed-by-reference, then, `someArray` will be a reference to `originalArray`.  And if that were true, then when we set `someArray = [1]`, that change should be reflected in `originalArray`, outside the function scope.  

But that's not what happens.  So... pass-by-reference is false??

Umm... no.

The problem here is that people seem to have completely juggled the idea of _an object_ with _a reference to that object_.  They're similar - but they're not identical, interchangeable concepts.  

A reference to an object allows us to perform operations on the original object.  But here's the critical part:  If we **_destroy_** that reference, we shouldn't be surprised when we can no longer perform operations on the original object.  

That's exactly what's happening in this example.  When `foo()` enters its instruction body, `someArray` is _absolutely_ a reference to `originalArray`.  But then, in the first line of the function's code, that reference is _destroyed_ (overwritten) by an entirely different object: `[1]`

And we can illustrate this concept just by adding a few lines of code:

```javascript
const foo = someArray => {
  someArray.push('pass');
  someArray.push('by');
  someArray = [1];
  someArray.push('reference');
};

let originalArray = [];
foo(originalArray);
console.log(originalArray);  // ['pass', 'by']
```

When the function first starts running, `someArray` is 100% a _reference_ to `originalArray`.  That's why `push()` updates the contents of `originalArray` - because as long as we have that _reference_, we can perform operations on the original object.

But on the third line of the function, we do this: `someArray = [1];`  That code doesn't overwrite the entire value of `originalArray`.  That line of code overwrites the _reference_ which originally pointed to `originalArray`.  Therefore, the first two `push()` statements are the only ones that are reflected back on the original array.

The key takeaway is this:<br/><br/>

> A _reference_ allows you to manipulate some original object.  But if you _overwrite_ the reference, you haven't overwritten the original object, you've just overwritten the reference _to_ that object.

<br/>This might be clearer if I add some comments to the code:

```javascript
const foo = someArray => {
  // someArray is currently A REFERENCE to 
  // originalArray
  someArray.push('pass'); // this uses THE REFERENCE to
  // originalArray to add an item to the array
  someArray.push('by'); // this uses THE REFERENCE to
  // originalArray to add an item to the array
  someArray = [1]; // this OVERWRITES the reference -
  // someArray is now [1] - with NO RELATION to
  // originalArray 
  someArray.push('reference'); // this adds an item to
  // the new [1] array, which has no relation to
  // originalArray
};

let originalArray = [];
foo(originalArray);
console.log(originalArray);  // ['pass', 'by']
```


In the interest of completeness, I'll also show @devdufutur's example:

```javascript
function reassign(someStuff) {
  someStuff = { someInt: 42 };
}

let three = { someInt: 3 };
console.log("before reassign", three); // { someInt: 3 }
reassign(three);
console.log("after reassign", three); // { someInt: 3 }
```

He used a traditional object, rather than an array, but the concept here is exactly the same.  The original `three` object remains unchanged because, in the first line of his `reassign()` function, he overwrote (destroyed) the reference to `three`.  

Notice that, in his example, he even used the term "reassign" - which is rather instructive.  Because when he writes `someStuff = { someInt: 3 };`, that LoC isn't reassigning the original object.  It's reassigning the `someStuff` variable from its reference to a brand new object, disconnected from the original object passed in the argument.

We can alter this example to highlight this same principle:

```javascript
function reassign(someStuff) {
  someStuff.someInt = -1;
  someStuff.thisIsPassBy = 'reference';
  someStuff = { someInt: 42 };
  someStuff.lost = 'reference';
}

let three = { someInt: 3 };
console.log("before reassign", three); // { someInt: 3 }
reassign(three);
console.log("after reassign", three);  // { someInt: -1, thisIsPassBy: 'reference' }
```

`someInt` is reassigned to `-1`.  That works because it's an operation _on the reference_ to `three`.  We can even add new keys, because adding the key is also an operation on the same `three` object (using the still-functioning _reference_).  

But our attempts to set `someInt` to `42` and `lost` to `'reference'` are **not** reflected on the original `three` object.  They can't be.  Because, when we tried to set `someInt` to `42`, _we overwrote the reference to `three`_.

Again, this might be a little clearer with some comments:

```javascript
function reassign(someStuff) {
  // someStuff is currently A REFERENCE to 'three'
  someStuff.someInt = -1; // this uses THE REFERENCE to
  // 'three' to update the value of someInt
  someStuff.thisIsPassBy = 'reference'; // this uses THE
  // REFERENCE to 'three' to add the thisIsPassBy key
  someStuff = { someInt: 42 }; // this OVERWRITES the
  // reference - someStuff is now { someInt: 42 } - with
  // NO RELATION to 'three'
  someStuff.lost = 'reference'; // this adds the 'lost'
  // key to the new { someInt: 42 } object, which has no
  // relation to 'three'
}

let three = { someInt: 3 };
console.log("before reassign", three); // { someInt: 3 }
reassign(three);
console.log("after reassign", three);  // { someInt: -1, thisIsPassBy: 'reference' }
```


<br/><br/>![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7vhe91tpwa9ci8jgdxnh.jpg)
## Buckets
FWIW, I _do_ understand that this is why some people (angrily) cling to the ideas that "Everything is passed by value!" and "Objects are passed by a value - that holds a _reference_."  And if that nomenclature helps _you_ understand what's going on, then _great_!

But when you look at the first example in this article and you stubbornly stamp your feet and declare, "They're **_both_** passed by value!" you're taking two examples, that clearly and demonstrably behave quite differently, and you're trying to wash away that difference with some unhelpful bromide.

I'm not asking anyone to think anything like me, but it helps me to think of variables not so much as "values" (which is already abstract as hell) or "references".  Instead, I just think of them as... buckets.

Once you've accounted for space constraints, you can put pretty much anything you want inside the bucket.  It can hold a quantifiable volume (number) or some printed text (a string) or _nothing_ (null) or... many other things.  

Buckets can also hold objects.  But when we try to pass the bucket to someone else, the recipient receives a note that says, "Hey, the original thing you're looking for is over there."  If you take the note out of the bucket and replace it with something else entirely (even if that "something else" is... another object), there's no way for the original object to reflect any of those changes.

A bucket (variable) can hold some _thing_ (like, a value) or it can hold a note that refers to something else.  If the bucket is holding a reference, and you _overwrite_ that reference with an assignment operator, you will wipe out the reference.  And you won't see any future changes reflected on the original object.