Primitive vs Reference Types in JS: Full Guide

Understand the fundamental difference between primitive and reference types in JavaScript. Learn how each is stored, copied, compared, and passed to functions with clear visual examples.

JavaScriptbeginner
11 min read

Every value in JavaScript falls into one of two categories: primitive or reference. This distinction controls how values are stored in memory, how they behave when copied or passed to functions, and how equality comparisons work. Misunderstanding this boundary is behind some of the most frustrating bugs in JavaScript, from objects mysteriously changing when you did not touch them to equality checks that return unexpected results.

This guide explains both categories with concrete code examples, shows you how to detect and handle each one, and gives you the patterns to avoid the classic reference-related traps. If you need a refresher on the JavaScript data types themselves, start there first.

The Two Categories

JavaScript has seven primitive types and one reference type:

CategoryTypesStored ByMutable?
PrimitiveString, Number, BigInt, Boolean, Undefined, Null, SymbolValueNo (immutable)
ReferenceObject (includes arrays, functions, dates, maps, sets)Reference (pointer)Yes (mutable)

The core difference: when you create a primitive, JavaScript stores the actual value. When you create an object, JavaScript stores a pointer (reference) to a memory location where the object's data lives.

How Primitives Work

Primitives are stored directly in the variable. When you copy a primitive, JavaScript creates a completely independent copy of the value.

javascriptjavascript
let originalPrice = 29.99;
let discountedPrice = originalPrice;
 
discountedPrice = 24.99;
 
console.log(originalPrice);   // 29.99 (unchanged)
console.log(discountedPrice); // 24.99 (independent copy)

Think of it like writing a number on two separate sticky notes. Changing what is written on one note does not affect the other. Each variable holds its own independent value.

Primitive Comparison

Primitives are compared by their actual value. Two primitives are equal if they hold the same value, regardless of how they were created.

javascriptjavascript
const a = "hello";
const b = "hello";
const c = "hell" + "o";
 
console.log(a === b); // true (same value)
console.log(a === c); // true (same value, different creation path)
 
const x = 42;
const y = 42;
console.log(x === y); // true

Passing Primitives to Functions

When you pass a primitive to a function, the function receives its own copy. Modifications inside the function do not affect the original.

javascriptjavascript
function doubleValue(num) {
  num = num * 2;
  console.log("Inside function:", num); // 20
}
 
let score = 10;
doubleValue(score);
console.log("Outside function:", score); // 10 (unchanged)

How Reference Types Work

Objects, arrays, and functions are reference types. The variable does not hold the object itself; it holds a reference (memory address) pointing to where the object is stored on the heap.

javascriptjavascript
const originalUser = { name: "Ada", role: "engineer" };
const copiedUser = originalUser;
 
copiedUser.role = "architect";
 
console.log(originalUser.role); // "architect" (both point to the same object!)
console.log(copiedUser.role);   // "architect"

Both originalUser and copiedUser point to the exact same object in memory. Modifying through one variable affects the other because there is only one object.

Array References

The same behavior applies to arrays:

javascriptjavascript
const teamA = ["Alice", "Bob", "Charlie"];
const teamB = teamA;
 
teamB.push("Diana");
 
console.log(teamA); // ["Alice", "Bob", "Charlie", "Diana"]
console.log(teamB); // ["Alice", "Bob", "Charlie", "Diana"]
console.log(teamA === teamB); // true (same reference)

Reference Comparison

Reference types are compared by their memory address, not their contents. Two objects with identical properties are NOT equal unless they are the same object.

javascriptjavascript
const obj1 = { name: "Ada", age: 25 };
const obj2 = { name: "Ada", age: 25 };
const obj3 = obj1;
 
console.log(obj1 === obj2); // false (different objects in memory)
console.log(obj1 === obj3); // true (same reference)

This surprises many beginners. Even though obj1 and obj2 have identical contents, they are different objects stored at different memory locations.

Common Trap: Object Equality

Never use === to compare object contents. It only checks if both variables point to the exact same object. To compare contents, use JSON.stringify() for simple objects or a deep comparison function for nested structures.

Passing References to Functions

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

javascriptjavascript
function updateEmail(user, newEmail) {
  user.email = newEmail; // Modifies the original object
}
 
const currentUser = { name: "Ada", email: "ada@old.com" };
updateEmail(currentUser, "ada@new.com");
 
console.log(currentUser.email); // "ada@new.com" (original was modified)

However, reassigning the parameter itself does not affect the original variable:

javascriptjavascript
function replaceUser(user) {
  user = { name: "New User", email: "new@email.com" }; // Only changes local reference
  console.log("Inside:", user.name); // "New User"
}
 
const myUser = { name: "Ada", email: "ada@email.com" };
replaceUser(myUser);
console.log("Outside:", myUser.name); // "Ada" (unchanged)

The distinction: modifying properties through the reference affects the original object. Reassigning the reference variable itself only changes the local copy of the pointer.

Visual Comparison: Copy Behavior

javascriptjavascript
// PRIMITIVES: Independent copies
let a = 10;
let b = a;    // b gets its own copy of "10"
b = 20;       // Only b changes
// a = 10, b = 20
 
// REFERENCES: Shared pointer
let x = { value: 10 };
let y = x;    // y points to the SAME object
y.value = 20; // Both see the change
// x.value = 20, y.value = 20
OperationPrimitiveReference
Copy (let b = a)Creates independent copyCopies the pointer (shared object)
Modify copyOriginal unchangedOriginal also changes
Comparison (===)Compares valuesCompares memory addresses
Pass to functionFunction gets a copy of the valueFunction gets a copy of the pointer
ImmutabilityAlways immutableMutable by default

Creating True Copies of Objects

Since assignment only copies the reference, you need specific techniques to create independent object copies.

Shallow Copy Methods

javascriptjavascript
const original = { name: "Ada", scores: [95, 87, 92] };
 
// Method 1: Spread operator
const copy1 = { ...original };
 
// Method 2: Object.assign
const copy2 = Object.assign({}, original);
 
// Method 3: Array spread (for arrays)
const originalArr = [1, 2, 3];
const arrCopy = [...originalArr];
 
copy1.name = "Grace"; // Does NOT affect original
console.log(original.name); // "Ada" (safe!)
Shallow Copies Have Limits

Shallow copies only clone the top level. Nested objects and arrays are still shared references. Modifying copy1.scores.push(100) would also modify original.scores because both point to the same inner array.

Deep Copy with structuredClone

For objects with nested structures, use structuredClone() (available in all modern browsers and Node.js 17+):

javascriptjavascript
const original = {
  name: "Ada",
  scores: [95, 87, 92],
  address: { city: "London", country: "UK" },
};
 
const deepCopy = structuredClone(original);
 
deepCopy.scores.push(100);
deepCopy.address.city = "Cambridge";
 
console.log(original.scores);      // [95, 87, 92] (unchanged!)
console.log(original.address.city); // "London" (unchanged!)

structuredClone handles nested objects, arrays, dates, maps, sets, and most built-in types. It does not handle functions, DOM nodes, or symbols.

Best Practices

Reference Safety Checklist

Follow these practices to avoid the most common reference-related bugs in production code.

Never assume assignment copies an object. When you write const b = a where a is an object, b is an alias, not a copy. Use spread, Object.assign, or structuredClone when you need independence.

Use structuredClone for nested data. Shallow copies (spread, Object.assign) only handle one level deep. If your object contains nested objects or arrays, always use structuredClone() for a true independent copy.

Avoid mutating function parameters. Instead of modifying objects passed to functions, return new objects. This prevents unexpected side effects and makes functions easier to test and reason about.

Freeze objects you want to protect. Use Object.freeze() to prevent any modification to an object's properties. Combine it with const for maximum protection.

Compare object contents with JSON.stringify or deep equal. Never use === to check if two objects have the same data. Either serialize with JSON.stringify() for simple cases or use a library's deep equality function for complex structures.

Common Mistakes and How to Avoid Them

Reference Type Pitfalls

These mistakes cause bugs that are difficult to trace because the mutation happens far from where the symptom appears.

Modifying shared arrays in state management. When multiple parts of your application reference the same array, pushing to it in one place updates it everywhere. Always create a new array: const updated = [...original, newItem].

Using === to compare objects with the same data. Two separate objects with identical properties are never equal with ===. This breaks if conditions and Set deduplication for objects.

Forgetting that array methods like sort() mutate the original. Methods like sort(), reverse(), splice(), and push() modify the array in place. If you need the original intact, sort a copy: const sorted = [...original].sort().

Shallow copying objects with nested state. Using the spread operator to copy an object that contains arrays or other objects creates a shallow copy where nested values are still shared.

javascriptjavascript
const state = { user: { name: "Ada" }, items: [1, 2, 3] };
const newState = { ...state };
 
newState.items.push(4);
console.log(state.items); // [1, 2, 3, 4] (original mutated!)

Next Steps

Understand memory storage

Learn how JavaScript stores primitive values on the stack and objects on the heap, and how garbage collection manages reference types.

Master object methods

Explore object creation, property access, and built-in methods like Object.keys(), Object.values(), and Object.entries() in the objects and arrays tutorial.

Learn [type conversion](/tutorials/programming-languages/javascript/javascript-type-conversion-coercion-explained) between categories

Understand how JavaScript converts between primitive types and objects during operations, and how to control this process.

Practice immutable patterns

Build a small state management module that never mutates data directly, using spread operators and structuredClone for all updates.

Rune AI

Rune AI

Key Insights

  • Primitives copy by value: assigning a primitive to a new variable creates a fully independent copy
  • Objects copy by reference: assigning an object copies the pointer, not the data, so both variables share the same object
  • Use structuredClone for deep copies: the spread operator only copies one level deep; nested objects remain shared
  • Object equality checks memory address: two objects with identical contents are not === equal unless they are the same object
  • Avoid mutating function arguments: return new objects instead of modifying parameters to prevent unexpected side effects
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between primitive and reference types in JavaScript?

Primitive types (String, Number, Boolean, Undefined, Null, BigInt, Symbol) store the actual value directly in the variable and are immutable. Reference types (Objects, Arrays, Functions) store a pointer to the data in memory, meaning multiple variables can point to the same data since only the reference is copied during assignment.

Why does modifying a copied object change the original in JavaScript?

When you assign an object to a new variable (`const b = a`), JavaScript copies the reference (memory address), not the object itself. Both variables point to the same object in memory. To create an independent copy, use the spread operator for shallow copies or `structuredClone()` for deep copies.

How do I compare two objects for equality in JavaScript?

The `===` operator only checks if two variables reference the same object in memory. To compare object contents, use `JSON.stringify(obj1) === JSON.stringify(obj2)` for simple objects (note: property order matters) or a deep equality utility from a library like Lodash for complex nested structures.

Are arrays primitive or reference types in JavaScript?

rrays are reference types in JavaScript. Technically, arrays are a special kind of object with numeric indices and a `length` property. This means assigning an array to another variable creates a shared reference, and methods like `push()` and `sort()` mutate the original array.

What is structuredClone and when should I use it?

`structuredClone()` is a built-in function (available since 2022 in all major browsers and Node.js 17+) that creates a deep copy of an object, including all nested objects and arrays. Use it whenever you need a completely independent copy of an object that contains nested data. It handles most built-in types but does not support functions or DOM nodes.

Conclusion

The primitive vs reference distinction is the single most important concept for understanding how JavaScript manages data. Primitives are stored by value, copied independently, and compared by content. Objects are stored by reference, share a pointer when copied, and are compared by memory address. Once you internalize this difference, bugs involving unexpected mutations, failed equality checks, and function side effects become easy to diagnose and prevent.