A Visual Guide to References in JavaScript
a.k.a. “pointers for JavaScript developers”
On Day 1 of learning to code, someone tells you “A variable is like a box. Writingthing = 5 puts5 in thething box”. And that’s notreally how variables work, but it’s good enough to get you going. It’s like in math class when they lie to you about the full picture, because the full picture would explode your brain right now.
Some time later though, you start to see weird problems. Variables changing when you didn’t change them. Ghosts in the machine.
“I thought I made a copy of that! Why did it change?” <– that right there is a reference bug!
By the end of this post, you’ll understand why that happens and how to fix it.
What is a Reference?
References are everywhere in JS, but they’re invisible. They just look like variables. Some languages, like C, call these things out explicitly aspointers, with their own syntax to boot. But JS doesn’t have pointers, at least not by that name. And JS doesn’t have any special syntax for them, either.
Take this line of JavaScript for example: it creates a variable calledword that stores the string “hello”.
letword="hello"
Notice howwordpoints to the box with the “hello”. There’s a level of indirection here. The variableis not the box. The variable points to the box. Let that sink in while you continue reading.
Now let’s give this variable a new value using the assignment operator=:
word="world"What’s actually happening here isn’t that the “hello” is being replaced by “world” – it’s more like an entirely new box is created, and theword is reassigned to point at the new box. (and at some point, the “hello” box is cleaned up by the garbage collector, since nothing is using it)
If you’ve ever tried to assign a value to a function parameter, you probably realized this doesn’t change anything outside the function.
The reason this happens is because reassigning a function parameter will only affect the local variable, not the original one that was passed in. Here’s an example:
functionreassignFail(word){// this assignment does not leak outword="world"}lettest="hello"reassignFail(test)console.log(test)// prints "hello"Initially, onlytest is pointing at the value “hello”.
Once we’re inside the function, though, bothtestandword are pointing at the same box.

After the assignment (word = "world"), theword variable points at its new value “world”. But wehaven’t changedtest. Thetest variable still points at its old value.

This is how assignment works in JavaScript. Reassigning a variable only changes that one variable. It doesn’t change any other variables that also pointed at that value. This is true whether the value is a string, boolean, number, object, array, function… every data type works this way.
Two Types of Types
JavaScript has two broad categories of types, and they have different rules around assignment and referential equality. Let’s talk about those.
Primitive Types in JavaScript
There are theprimitive types like string, number, boolean (and also symbol, undefined, and null). These ones areimmutable. a.k.a.read-only,can’t be changed.
When a variable holds one of these primitive types, you can’t modify the value itself. You can onlyreassign that variable to a new value.
The difference is subtle, but important!
Said another way, when the value inside a box is a string/number/boolean/symbol/undefined/null, you can’t change the value. You can only create new boxes.
It doesnot work like this…
This is why, for example, all of the methods on strings return a new string instead of modifying the string, and if you want that new value, you’ve gotta store it somewhere.
letname="Dave"name.toLowerCase();console.log(name)// still capital-D "Dave"name=name.toLowerCase()console.log(name)// now it's "dave"Every other type: Objects, Arrays, etc.
The other category is theobject type. This encompasses objects, arrays, functions, and other data stuctures like Map and Set. They are all objects.
The big difference from primitive types is that objects aremutable! You can change the value in the box.

Immutable is Predictable
If you pass a primitive value into a function, the original variable you passed in is guaranteed to be left alone. The function can’t modify what’s inside it. You can rest assured that the variable will always be the same after calling a function – any function.
But with objects and arrays (and the other object types), you don’t have that assurance. If you pass an object into a function, that function could change your object. If you pass an array, the function could add new items to it, or empty it out entirely.
So this is one reason why a lot of people in the JS community try towrite code in animmutable way: it’s easier to figure out what the code does when you’re sure your variables won’t change unexpectedly. If every function is written to be immutable by convention, you never need to wonder what will happen.
A function that doesn’t change its arguments, or anything outside of itself, is called apure function. If it needs to change something in one of its arguments, it’ll do that by returning a new value instead. This is more flexible, because it means the calling code gets to decide what to do with that new value.
Recap: Variables Point to Boxes, and Primitives are Immutable
We’ve talked about how assigning or reassigning a variable effectively “points it at a box” that contains a value. And how assigning aliteral value (as opposed to a variable) creates a new box and points the variable at it.
letnum=42letname="Dave"letyes=trueletno=falseletperson={ firstName:"Dave", lastName:"Ceddia"}letnumbers=[4,8,12,37]
This is true for primitive and object types, and it’s true whether it’s the first assignment or a reassignment.
We’ve talked about how primitive types are immutable. You can’t change them, you can only reassign the variable to something else.
Now let’s look at what happens when you modify a property on an object.
Modifying the Contents of the Box
We’ll start with abook object representing a book in a library that can be checked out. It has atitle and anauthor and anisCheckedOut flag.
letbook={ title:"Tiny Habits", author:"BJ Fogg", isCheckedOut:false}Here’s our object and its values as boxes:

And then let’s imagine we run this code:
book.isCheckedOut=trueHere’s what that does to the object:
Notice how thebook variable never changes. It continues to point at the same box, holding the same object. It’s only one of that object’sproperties that has changed.
Notice how this follows the same rules as earlier, too. The only difference is that the variables are now inside an object. Instead of a top-levelisCheckedOut variable, we access it asbook.isCheckedOut, but reassigning it works the exact same way.
The crucial thing to understand is that theobject hasn’t changed. In fact, even if we made a “copy” of the book by saving it in another variable before modifying it, westill wouldn’t be making a new object.
letbook={ title:"Tiny Habits", author:"BJ Fogg", isCheckedOut:false}letbackup=bookbook.isCheckedOut=trueconsole.log(backup===book)// true!console.log(backup.isCheckedOut)// also true!!The linelet backup = book will point thebackup variable at the existing book object. (it’s not actually a copy!)
Here’s how that would play out:
Theconsole.log at the end further proves the point:book is still equal tobackup, because they point at the same object, and because modifying a property onbook didn’t change theshell of the object, it only changed the internals.
Variables always point to boxes, never to other variables. When we assignbackup = book, JS immediately does the work to look up whatbook points to, and pointsbackup to the same thing. It doesn’t pointbackup tobook.
This is nice: it means that every variable is independent, and we don’t need to keep a sprawling map in our heads of which variables point to which other ones. That would be very hard to keep track of!
Mutating an Object in a Function
Wayyy back up in the intro I alluded to changing a variable inside a function, and how that sometimes “stays inside the function” and other times it leaks out into the calling code and beyond.
We already talked about how reassigning a variable inside a function will not leak out, as long as it’s a top-level variable likebook orhouse and not a sub-property likebook.isCheckedOut orhouse.address.city.
functiondoesNotLeak(word){// this assignment does not leak outword="world"}lettest="hello"doesNotLeak(test)console.log(test)// prints "hello"
And anyway, this example used a string, so we couldn’t modify it even if we tried. (because strings are immutable, remember)
But what if we had a function that received an object as an argument? And then changed a property on it?
functioncheckoutBook(book){// this change will leak out!book.isCheckedOut=true}letbook={ title:"Tiny Habits", author:"BJ Fogg", isCheckedOut:false}checkoutBook(book);Here’s what happens:
Look familiar? It’s the same animation from earlier, because the end result is exactly the same! It doesn’t matter whetherbook.isCheckedOut = true occurs inside a function or outside, because that assignment will modify the internals of thebook object either way.
If you want toprevent that from happening, you need to make a copy, and then change the copy.
functionpureCheckoutBook(book){letcopy={...book}// this change will only affect the copycopy.isCheckedOut=true// gotta return it, otherwise the change will be lostreturncopy}letbook={ title:"Tiny Habits", author:"BJ Fogg", isCheckedOut:false}// This function returns a new book,// instead of modifying the existing one,// so replace `book` with the new checked-out onebook=pureCheckoutBook(book);If you want to learn more about writing immutable functions like this, read myguide to immutability. It’s written with React and Redux in mind but most of the examples are plain JavaScript.
References in the Real World
With your newfound knowledge of references, let’s look at a few examples that could cause problems. See if you can spot the problem before reading the soltuion.
DOM Event Listeners
Quick background on how the event listener functions work: to add an event listener, calladdEventListener with the event name and a function. To remove an event listener, call theremoveEventListener with thesame event name and thesame function, as in the same function reference. (otherwise the browser can’t possibly know which function to remove, since an event can have multiple functions attached to it)
Have a look at this code. Is this using the add/remove functions correctly?
document.addEventListener('click',()=>console.log('clicked'));document.removeEventListener('click',()=>console.log('clicked'));…
…
…
…
Figured it out?
This code will never remove the event listener, because those two arrow functions are not referentially equal. They’re not the same function, even though they are identical as far as syntax goes.
Every time you write an arrow function() => { ... } or a regular functionfunction whatever() { ... }, thatcreates a new object (functions are objects, remember).
Prove it! Try this in the console:
leta=()=>{}letb=()=>{}console.log(a===b)It’ll printfalse! Every new object (array, function, Set, Map, etc.) lives in a brand new box, unequal to every other box.
To make the event listener example work correctly, store the function in a variable first, and pass that same variable to both add and remove.
constonClick=()=>console.log('clicked');document.addEventListener('click',onClick);document.removeEventListener('click',onClick);Unintended Mutation
Let’s look at another one. Here’s a function that finds the smallest item in an array by sorting it first, and taking the first item.
functionminimum(array){array.sort();returnarray[0]}constitems=[7,1,9,4];constmin=minimum(items);console.log(min)console.log(items)What does this print?
…
…
…
…
If you said1 and[7, 1, 9, 4], you’re only half right ;)
The.sort() method on arrayssorts the arrayin place, meaning it changes the order on the original array without copying it.
This example prints1 and[1, 4, 7, 9].
Now, thismight be what you wanted. But probably not, right? When you call aminimum function, you don’t expect it to rearrange the items in your array.
This kind of behavior can be especially confusing when the function lives in another file, or in a library, where the code isn’t right in front of you.
To fix this, make a copy of the array before sorting it, like in the code below. Here we’re using thespread operator to make a copy of the array (the[...array] part). This is actually creating a brand new array and then copying in every element from the old one.
functionminimum(array){constnewArray=[...array].sort();returnnewArray[0]}Go Forth and Reference Well
This stuff comes upall the time, but it’s also one of those things you can kind of muddle through without knowing quite how it works.
It can take a little while to wrap your brain around the concept of “pointers”, variables pointing to values, and keeping references straight. If your brain feels like it’s in a fog right now, bookmark this article and come back in a week.
Once you get it, you’ve got it, and it’ll make all of your JS development go more smoothly.
This article is the first in a series on data structures and algorithms in JS. Next up isLinked lists in JavaScript! Now that you know how references work, linked lists will be way easier to understand.
I’m working on the next article in the series, on binary trees. Drop your email in the box if you want to be notified when it’s out!