JavaScript Interview Prep
Miscellaneous

Pass by Value vs Pass by Reference

Text Message vs Google Docs Link

LinkedIn Hook

"JavaScript passes objects by reference." — wrong answer.

JavaScript passes EVERYTHING by value. For primitives, the value itself is copied. For objects, the VALUE being copied happens to be a reference (a memory address). So the function gets its own pointer to the same heap object — that's pass-by-reference-value, not true pass-by-reference.

The proof is a three-line swap function. If JavaScript had true pass-by-reference, a swap function would swap the caller's variables. It doesn't. Because reassigning a parameter only rebinds the LOCAL copy of the pointer.

In this lesson you'll learn why 90% of developers get this wrong, the text-message vs Google-Docs-link intuition, why mutating works but reassignment doesn't, and how to write functions that never accidentally mutate their inputs using spread, slice, and structuredClone.

This is the final lesson of Chapter 14 and the final lesson of the entire course. 14 chapters. 87 lessons. The series is now complete.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #PassByValue #Memory #Frontend #CodingInterview #WebDevelopment


Pass by Value vs Pass by Reference thumbnail


What You'll Learn

  • Why JavaScript is always pass-by-value (even for objects)
  • Why mutation through a parameter works but reassignment doesn't
  • How to defensively pass objects without risking mutation (...spread, slice, structuredClone)

Think of it like this: when you send someone a text message (primitive), they get their own copy — they can edit it without affecting your original. But when you share a Google Docs link (object), you're both looking at the same document — their edits change what you see too.

Primitives Are Pass by Value

When you pass a primitive (number, string, boolean, null, undefined, symbol, bigint) to a function, the function receives a copy. Changes to the copy don't affect the original:

function increment(num) {
  num = num + 1;
  console.log("Inside:", num); // 11
}

let x = 10;
increment(x);
console.log("Outside:", x); // 10 — unchanged!

// Same with strings
function shout(str) {
  str = str.toUpperCase();
  console.log("Inside:", str); // "HELLO"
}

let greeting = "hello";
shout(greeting);
console.log("Outside:", greeting); // "hello" — unchanged!

Objects Are "Pass by Reference Value"

This is where 90% of developers get confused. JavaScript does NOT have true pass by reference (like C++ with &). Instead, it passes a copy of the reference (the memory address). The function gets its own copy of the pointer, but both pointers point to the same object:

// The function CAN mutate the object through the reference
function addAge(person) {
  person.age = 25; // mutates the original object!
}

const user = { name: "Rakibul" };
addAge(user);
console.log(user); // { name: "Rakibul", age: 25 } — MUTATED!

// BUT — the function CANNOT replace the reference itself
function replace(person) {
  person = { name: "Someone Else", age: 99 }; // reassigns LOCAL copy
  console.log("Inside:", person); // { name: "Someone Else", age: 99 }
}

const user2 = { name: "Rakibul" };
replace(user2);
console.log("Outside:", user2); // { name: "Rakibul" } — UNCHANGED!

This is the KEY distinction:

  • True pass by reference (C++): The function could replace the ORIGINAL variable's value. Reassignment inside the function would affect the caller.
  • Pass by reference value (JavaScript): The function gets a copy of the pointer. It CAN follow the pointer and mutate the object. But reassigning the parameter only changes the local copy — the caller's variable still points to the original object.

The Definitive Proof

function swap(a, b) {
  let temp = a;
  a = b;
  b = temp;
  console.log("Inside — a:", a, "b:", b); // swapped
}

let x = { value: 1 };
let y = { value: 2 };

swap(x, y);
console.log("Outside — x:", x, "y:", y);
// x: { value: 1 }, y: { value: 2 } — NOT SWAPPED!

// If JavaScript had true pass by reference,
// x and y would be swapped after the function call.
// They aren't — proving it's pass by "reference value" (copy of pointer).

Mutation Proof with Arrays

// Mutation through reference — WORKS
function pushItem(arr, item) {
  arr.push(item); // mutates original array
}

const myArr = [1, 2, 3];
pushItem(myArr, 4);
console.log(myArr); // [1, 2, 3, 4] — MUTATED!

// Reassignment of reference — DOESN'T affect original
function replaceArray(arr) {
  arr = [99, 100]; // reassigns local parameter
}

replaceArray(myArr);
console.log(myArr); // [1, 2, 3, 4] — UNCHANGED!

How to Avoid Unintended Mutation

When you want functions that don't mutate inputs:

// Method 1: Spread operator (shallow copy)
function addProperty(obj, key, value) {
  return { ...obj, [key]: value }; // new object, original untouched
}

const original = { name: "Rakibul" };
const updated = addProperty(original, "age", 25);
console.log(original); // { name: "Rakibul" } — safe
console.log(updated);  // { name: "Rakibul", age: 25 }

// Method 2: structuredClone (deep copy)
function processData(data) {
  const copy = structuredClone(data); // deep clone
  copy.nested.value = "modified";
  return copy;
}

const deepObj = { nested: { value: "original" } };
const result = processData(deepObj);
console.log(deepObj.nested.value);  // "original" — safe
console.log(result.nested.value);   // "modified"

// Method 3: Array methods that return new arrays
function removeLast(arr) {
  return arr.slice(0, -1); // new array
  // NOT arr.pop() — that mutates
}

Common Interview Misconceptions

// Misconception 1: "Objects are pass by reference"
// WRONG — they are pass by reference VALUE (copy of pointer)

// Misconception 2: "Reassigning a parameter changes the original"
function tryReassign(obj) {
  obj = { completely: "new" }; // only changes local binding
}
let myObj = { original: true };
tryReassign(myObj);
console.log(myObj); // { original: true } — proof it's not true pass-by-ref

// Misconception 3: "const makes objects immutable"
const frozen = { a: 1 };
frozen.b = 2;          // WORKS — const prevents reassignment, not mutation
console.log(frozen);   // { a: 1, b: 2 }
// frozen = {};        // THIS would fail — reassignment blocked

// Misconception 4: "Primitives in objects are pass by reference"
const config = { count: 0 };
let count = config.count; // copies the VALUE (0)
count++;
console.log(config.count); // 0 — reading a primitive copies it

Pass by Value vs Pass by Reference visual 1


Common Mistakes

  • Saying "objects are pass by reference" in an interview. JavaScript is ALWAYS pass by value; for objects, the value being copied is a reference. True pass-by-reference would let a swap(a, b) function swap the caller's variables — and in JavaScript it can't.
  • Expecting parameter reassignment to replace the caller's object. function replace(x) { x = {...} } only rebinds the LOCAL parameter. The caller's variable still points to the original object.
  • Passing an array or object into a function and being surprised when push / sort / direct property writes mutate the caller's data. The function and caller share the same heap object — if you need safety, spread it ([...arr], {...obj}) or structuredClone(obj) first.

Interview Questions

Q: Is JavaScript pass by value or pass by reference?

JavaScript is ALWAYS pass by value. For primitives, the value itself is copied. For objects, the reference (memory address) is copied — so it's "pass by reference value." The function gets its own copy of the pointer. It can use the pointer to mutate the object, but reassigning the parameter only changes the local copy. The caller's variable is unaffected.

Q: How would you prove JavaScript isn't true pass by reference?

Write a swap function. In a true pass-by-reference language, swapping two parameters inside a function would swap the caller's variables. In JavaScript, it doesn't — the caller's variables remain unchanged. This proves the function receives copies of the references, not the references themselves.

Q: What's the safest way to pass an object to a function without risk of mutation?

Pass a deep clone: structuredClone(obj). For shallow objects, the spread operator works: { ...obj }. For arrays: [...arr] or arr.slice(). The function then works on an independent copy.

Q: If you pass an array to a function and the function does arr.push(4), does the original array change?

Yes. The function receives a copy of the reference, which points to the same array. Mutations through the reference (like push) affect the original. Only reassignment (arr = [...]) would not affect the original.


Quick Reference — Cheat Sheet

PASS BY VALUE vs REFERENCE — QUICK MAP

Primitives   -> pass by VALUE (copy of the value)
               number, string, boolean, null, undefined, symbol, bigint

Objects      -> pass by "REFERENCE VALUE" (copy of the pointer)
               object, array, function, Map, Set, Date, ...

What the function can do:
  mutate through the reference:  obj.prop = x   -> affects caller
  push / pop / sort on array:    arr.push(x)    -> affects caller
  reassign the parameter:        obj = {...}    -> LOCAL only, caller unchanged

Swap-function test:
  swapping parameters inside a function does NOT swap caller's vars
  -> proof JS is NOT true pass-by-reference

Defensive passing (avoid mutation):
  shallow:  { ...obj }, [...arr], arr.slice()
  deep:     structuredClone(obj)
  non-mutating array ops: map, filter, slice, concat (avoid push/pop/sort)

Previous: Immutability -> The Museum-Exhibit Mindset Next: None (last lesson of the course)


This is Lesson 14.5 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page