Primitive vs Reference Types in JS: Full Guide

Primitives are copied by value. Objects are copied by reference. Learn the critical difference, see it in action, and avoid the most common copy-and-mutate bug in JavaScript.

6 min read

Primitive vs reference JavaScript values behave in two fundamentally different ways when you assign them to variables or pass them to functions: primitives are copied by value, and objects are copied by reference. Understanding this distinction prevents one of the most common and confusing bugs in the language.

BehaviorPrimitive typesReference types
Copied byValueReference
Examplesstring, number, boolean, null, undefined, symbol, bigintObject, Array, Function, Date, Map, Set
Changing a copyDoes not affect originalAffects original
ComparisonCompares valuesCompares references (memory location)
StorageDirectly in the variableVariable stores a pointer to the object

How Primitives Work: Copy by Value

When you assign a primitive value to a new variable, JavaScript creates a fresh copy of that value. The two variables are completely independent. Changing one has no effect on the other.

javascriptjavascript
let color = "blue";
let favoriteColor = color;
 
favoriteColor = "green";
console.log(color);          // "blue"
console.log(favoriteColor);  // "green"

The variable favoriteColor received a copy of the value "blue", not a link to the original variable. When favoriteColor was reassigned to "green", the original color variable remained untouched. This is intuitive behavior that matches how most people expect variables to work.

Primitives also compare by value. Two primitives are equal if they contain the same data, regardless of where they came from:

javascriptjavascript
let a = 100;
let b = 100;
console.log(a === b); // true (same value)
 
let x = "hello";
let y = "hello";
console.log(x === y); // true (same value)

How Objects Work: Copy by Reference

When you assign an object to a new variable, JavaScript does not create a copy of the object. Instead, both variables point to the same object in memory. Changes through one variable are visible through the other.

javascriptjavascript
let user = { name: "Alex", score: 0 };
let player = user;
 
player.score = 100;
console.log(user.score); // 100 (also changed!)

The variable player did not receive a copy of the object. It received a reference to the same object that user points to. When you modify player.score, you are modifying the single shared object, so user.score also reflects the change.

Object references pointing to shared memory

Both variables point to the same object in memory. There is only one object, with two names that can reach it. This is why modifying the object through one name affects what you see through the other.

Objects also compare by reference, not by value. Two objects with identical contents are not equal because they occupy different memory locations:

javascriptjavascript
let obj1 = { value: 10 };
let obj2 = { value: 10 };
console.log(obj1 === obj2); // false (different objects in memory)
 
let obj3 = obj1;
console.log(obj1 === obj3); // true (same object in memory)

This is a frequent source of confusion. Two objects that look identical are not equal unless they are literally the same object.

Arrays Are Reference Types Too

Arrays are objects in JavaScript, so they follow the same reference behavior. Assigning an array to a new variable shares the array, it does not copy it.

javascriptjavascript
let numbers = [1, 2, 3];
let copy = numbers;
 
copy.push(4);
console.log(numbers); // [1, 2, 3, 4]
console.log(copy);    // [1, 2, 3, 4]

The push method modified the shared array, visible through both variables. If you need an independent copy of an array, use the spread operator to create a new array with the same elements:

javascriptjavascript
let numbers = [1, 2, 3];
let realCopy = [...numbers];
 
realCopy.push(4);
console.log(numbers);  // [1, 2, 3] (unchanged)
console.log(realCopy); // [1, 2, 3, 4]

The spread operator creates a brand new array, so mutations to realCopy do not affect the original numbers.

Functions and Reference Types

When you pass an object to a function, the function receives a reference to the same object, not a copy. This means a function can modify the original object.

javascriptjavascript
function addPoint(player) {
  player.score += 1;
}
 
let user = { name: "Alex", score: 0 };
addPoint(user);
console.log(user.score); // 1

The addPoint function modified the original object because it received a reference. This is intentional and useful: it allows functions to operate on shared data without returning and reassigning values. But it also means you must be careful not to accidentally mutate objects you did not intend to change.

Primitives passed to functions are safe from this. The function receives a copy of the value, so the original is never affected:

javascriptjavascript
function increment(n) {
  n += 1;
  return n;
}
 
let count = 5;
let result = increment(count);
console.log(count);  // 5 (unchanged)
console.log(result); // 6

How to Copy Objects Safely

To create an independent copy of an object, you need to explicitly clone it. The spread operator creates a shallow copy, which works for objects with only primitive properties:

javascriptjavascript
let original = { name: "Alex", score: 0 };
let clone = { ...original };
 
clone.score = 100;
console.log(original.score); // 0 (unchanged)

For objects with nested objects, the spread operator only copies the top level. Nested objects are still shared by reference. For a complete deep copy, use structuredClone, which is available in all modern browsers and Node.js:

javascriptjavascript
let original = { user: { name: "Alex" }, scores: [1, 2, 3] };
let deepCopy = structuredClone(original);
 
deepCopy.user.name = "Jordan";
console.log(original.user.name); // "Alex" (unchanged)

For a deeper look at how JavaScript manages memory for these types, see the guide on how JavaScript stores primitive values in memory. For the complete list of data types, read the JavaScript data types guide.

Rune AI

Rune AI

Key Insights

  • Primitives (string, number, boolean, etc.) are copied by value.
  • Objects (arrays, plain objects, functions) are copied by reference.
  • Changing a primitive copy never affects the original.
  • Changing an object through one reference affects all references to it.
  • Use spread syntax or structuredClone to create independent object copies.
RunePowered by Rune AI

Frequently Asked Questions

How do I copy an object without sharing the reference?

Use the spread operator for shallow copies: const copy = { ...original }. For deep copies with nested objects, use structuredClone(original) or JSON.parse(JSON.stringify(original)) for simple cases without functions or dates.

Are arrays primitive or reference types?

Arrays are reference types. When you assign an array to a new variable, both variables point to the same array in memory. Changes through one variable are visible through the other.

Why does changing a const object's property work?

const prevents reassigning the variable itself, not modifying the object it points to. The variable binding is constant, but the object's contents are still mutable.

Conclusion

Primitives are copied by value: changing one copy never affects the original. Objects are copied by reference: multiple variables can point to the same object in memory, and changes through one variable are visible through all of them. This is the single most important behavioral difference in the JavaScript type system.