Introduction to Parameter Passing in JavaScript

Understanding how JavaScript passes arguments to functions is one of the most fundamental concepts in the language. It directly impacts how data is mutated, how bugs are introduced during state manipulation, and how memory is managed.

The short, definitive answer is: JavaScript is always pass-by-value.

However, this answer frequently confuses developers because variables that hold objects and arrays appear to behave as if they are passed by reference. To master this concept, we need to separate how primitive values are handled from how reference values (objects) are handled under the hood.

1. The Core Rule: JavaScript is Always Pass-by-Value

When you pass a variable into a function, JavaScript takes a copy of that variable’s value and assigns that copy to the function’s parameter.

  • For primitives (numbers, strings, booleans, null, undefined, symbols, bigints), the value stored in the variable is the actual data itself.

  • For objects (including arrays, functions, dates, and custom classes), the value stored in the variable is a reference (or memory address) pointing to where the object lives in memory.

Because JavaScript copies this value, you are always copying something—either the primitive value itself or the reference address pointing to an object. Therefore, the mechanism is strictly pass-by-value.

2. Pass-by-Value with Primitives

When working with primitive data types, the variable holds the actual value. Passing it to a function creates an entirely independent copy. Reassigning or modifying the parameter inside the function has zero effect on the original variable outside the function.

Example: Primitives

JavaScript

function changeValue(num, text, flag) {
  num = 100;
  text = "Modified";
  flag = false;
  
  console.log("Inside function:", num, text, flag); 
  // Output: 100, "Modified", false
}

let originalNum = 10;
let originalText = "Original";
let originalFlag = true;

changeValue(originalNum, originalText, originalFlag);

console.log("Outside function:", originalNum, originalText, originalFlag); 
// Output: 10, "Original", true (Unchanged!)

Why this happens: originalNum holds 10. When passed to changeValue, the engine creates a separate memory slot for the parameter num and copies 10 into it. Changing num inside the function only alters that local copy.

3. The “Pass-by-Reference” Illusion with Objects

When working with objects, developers often assume JavaScript switches to pass-by-reference because mutating an object’s properties inside a function changes the original object globally.

Example: Mutating Object Properties

JavaScript

function updateUserName(userObj) {
  userObj.name = "Alice Smith"; // Mutating a property
  console.log("Inside function:", userObj.name); 
  // Output: "Alice Smith"
}

let person = { name: "John Doe", age: 30 };

updateUserName(person);

console.log("Outside function:", person.name); 
// Output: "Alice Smith" (The original object was changed!)

Why this happens (Pass-by-Value of the Reference):

  1. The variable person does not contain the object itself; it contains a reference (an address pointing to the object in heap memory).

  2. When person is passed into updateUserName, JavaScript copies that reference value into the parameter userObj.

  3. Now, both person and userObj point to the exact same location in memory.

  4. When you modify userObj.name, you are dereferencing that address and changing the properties of the object stored at that memory location. Naturally, looking at the same object through the person variable reveals the updated property.

4. Proving JavaScript is Pass-by-Value (Reassignment Test)

If JavaScript were truly pass-by-reference, reassigning the parameter variable itself to a brand-new object inside the function would overwrite the original variable outside the function. Let’s test what actually happens:

JavaScript

function reassignObject(obj) {
  // Reassigning the parameter to a completely new object
  obj = { name: "Bob", age: 40 };
  console.log("Inside function:", obj); 
  // Output: { name: "Bob", age: 40 }
}

let currentPerson = { name: "Charlie", age: 25 };

reassignObject(currentPerson);

console.log("Outside function:", currentPerson); 
// Output: { name: "Charlie", age: 25 } (Unchanged!)

Breakdown of the Test:

  • currentPerson holds a reference address (e.g., Ref#101).

  • That reference is copied into obj. Inside the function, obj also equals Ref#101.

  • When we write obj = { name: "Bob", age: 40 }, we allocate a new object in memory (e.g., Ref#202) and point the local obj variable to it.

  • The outer variable currentPerson still points to Ref#101. Because currentPerson was never touched or modified, it remains completely unaffected. This proves beyond doubt that the reference itself was passed by value (as a copy).

5. Practical Implications: Defensive Coding

Because objects are passed by reference, unexpected side effects are common in large applications. To prevent functions from accidentally modifying your data, you should use Immutability patterns.

A. Shallow Cloning

Use the spread operator (...) or Object.assign() to create a new object before passing it or modifying it. This creates a new memory reference for the top-level properties.

JavaScript

const original = { a: 1, b: 2 };
const copy = { ...original };
copy.a = 99; 

console.log(original.a); // 1 (Safe!)

B. The Deep Clone Problem

Note that a shallow clone only goes one level deep. If your object contains nested objects, those nested objects are still passed by reference. For deeply nested structures, you may need a deep clone.

JavaScript

const user = { id: 1, meta: { color: "red" } };
const shallow = { ...user };

shallow.meta.color = "blue"; 
console.log(user.meta.color); // "blue" (Nested objects were NOT cloned!)

Categorized in:

Javascript, Js Basic,