Passing by value vs passing by reference in JavaScript

HighIntermediateJavascript
Quick Answer

JavaScript is pass-by-value, but for objects the copied value is a reference to the same object. That is why object property mutations can be visible outside a function, while reassignment is not. Interview-quality answers explain call-by-sharing with concrete mutation vs reassignment examples.

Answer

The Core Idea

JavaScript arguments are passed by value. Always.

The confusion starts because object values are references (addresses). So when you pass an object, JavaScript copies the reference value, not the whole object. Both caller and callee can point to the same object in memory.

This model is often called call-by-sharing.

Case

What gets copied into the parameter

If you mutate inside function

If you reassign the parameter

Primitive (number/string/boolean/etc.)

The primitive value itself

Not applicable (primitives are immutable)

Only local parameter changes

Object/Array/Function

A reference value to the same object

Visible outside (same underlying object)

Only local parameter points elsewhere

Mutation and reassignment are different operations; most bugs come from mixing them.
JAVASCRIPT
// Primitive example: local change only
function bump(x) {
  x = x + 1;
  return x;
}

let count = 10;
const next = bump(count);

console.log(next);  // 11
console.log(count); // 10 (unchanged)
                  

Objects: Mutation vs Reassignment

For objects, these two lines are very different:

  • obj.name = 'X' -> mutates shared object
  • obj = { name: 'X' } -> rebinds local parameter only
JAVASCRIPT
function updateUser(user) {
  user.name = 'Ada';             // mutation: affects caller
}

function replaceUser(user) {
  user = { name: 'Grace' };      // reassignment: local only
}

const original = { name: 'Lin' };
updateUser(original);
console.log(original.name); // 'Ada'

replaceUser(original);
console.log(original.name); // still 'Ada'
                  
JAVASCRIPT
function addItem(list) {
  list.push('x');                // mutation: visible outside
}

function resetList(list) {
  list = [];                     // reassignment: local only
}

const items = ['a', 'b'];
addItem(items);
console.log(items); // ['a', 'b', 'x']

resetList(items);
console.log(items); // ['a', 'b', 'x']
                  

How to Avoid Side Effects

If a function should not alter caller state, create a new value inside the function and return it. Use shallow or deep copy strategy based on data shape.

JAVASCRIPT
function safeRename(user, nextName) {
  return { ...user, name: nextName }; // non-mutating top-level update
}

const a = { name: 'Lin', meta: { role: 'dev' } };
const b = safeRename(a, 'Ada');

console.log(a.name); // 'Lin'
console.log(b.name); // 'Ada'
                  

Common Pitfalls

  • Saying 'objects are passed by reference' as a full explanation (incomplete).
  • Forgetting that spread/object copy is shallow, so nested objects remain shared.
  • Accidentally mutating function inputs in reducers, hooks, or utility helpers.
  • Misusing const: it prevents rebinding, not internal object mutation.

Interview prompt

Strong answer pattern

Is JS pass-by-value or pass-by-reference?

Pass-by-value; object values are references, so copied references can mutate shared objects.

Why did my function change external state?

Because it mutated a shared object via copied reference value.

How do you prevent this?

Return new values (immutable updates), and test nested references explicitly.

Use this phrasing in interviews to sound precise and senior.

Practical scenario
A pricing helper receives a cart object and unexpectedly mutates line items, causing stale UI and failed snapshot tests in checkout flows.

Common pitfalls

      • Mutating objects passed into utility functions.
      • Assuming reassignment and mutation are equivalent.
      • Only testing return values, not input integrity.
Trade-off or test tip
In critical paths, add tests that assert original inputs remain unchanged (deep equality + reference checks where needed).

Still so complicated?

Think of giving someone your home address, not your house. If they repaint the house, both of you see it changed. If they write down a different address, your house stays the same.

Summary

JavaScript uses pass-by-value for all arguments. For primitives, that copied value is the data itself. For objects, that copied value is a reference to shared data. So mutation can leak across scopes, while parameter reassignment stays local.

Similar questions
Guides
Preparing for interviews?

Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.