Pass by Value vs. Reference in JavaScript

This Article is Over Eleven Years Old...
Things can and do move very quickly in tech, which means that tech-related articles go out of date almost as soon as they have been written and published. If you are looking for up-to-date technical advice or opinion, it is unlikely that you will find it on this page.
You may find that my recent articles are more relevant, and you are always welcome to drop me a line if you have a specific technical problem you are trying to solve.
Ask a room of developers whether JavaScript passes things by value or by reference and you will often get a messy answer very quickly. Beginners get caught because the language feels like it does one thing with numbers and strings, another with objects and arrays, and then something stranger again when functions receive those values as arguments.
The practical problem is easier to recognise than the terminology. You pass an object into a function, change something inside it, and the original value outside the function appears to have changed too. Then you pass a number into a function, reassign it, and nothing outside changes at all.
That makes it feel as though JavaScript has two incompatible calling rules.
It does not, but the mental model needs to be precise.
Start with the Simple Case: Primitives Feel Independent
When you pass a number, string, or boolean into a function, it behaves as if the function received its own separate copy of that value.
const updateCount = (count: number): void => { count = count + 1; console.log(count);};let total = 5;updateCount(total);console.log(total);Inside the function, count becomes 6. Outside the function, total is still 5.
This is why beginners often hear "primitives are passed by value". It is a useful shorthand because reassignment inside the function does not reach back and rewrite the outer variable.
Objects and Arrays Behave Differently
Now compare that with an object:
type User = { name: string;};const renameUser = (user: User): void => { user.name = 'Ellie';};const currentUser = { name: 'Maddie',};renameUser(currentUser);console.log(currentUser.name);After the function runs, currentUser.name is 'Ellie'.
That makes many people say "objects are passed by reference". It feels true because changes inside the function show up outside too.
The trouble is that this phrasing can mislead you if you do not understand what is actually being shared.
The Variable Itself is Not Being Shared in the Same Way
Consider this version:
type User = { name: string;};const replaceUser = (user: User): void => { user = { name: 'Ellie', };};const currentUser = { name: 'Maddie',};replaceUser(currentUser);console.log(currentUser.name);This time currentUser.name is still 'Maddie'.
Why? Because reassigning the parameter variable inside the function is not the same as mutating the object it points to.
That distinction is the heart of the issue:
- mutating a shared object affects the same underlying object
- reassigning the local parameter only changes what that local variable points at
A More Careful Mental Model
The cleanest practical model is this:
JavaScript passes values into functions. For objects and arrays, the value being passed is a reference‑like value to an underlying object in memory, which is why the function can mutate the same object the caller can still see.
You do not need to become overly philosophical about the wording to use this correctly day to day. The important part is understanding the two different operations:
- changing the object
- changing the variable
Those are not the same thing.
Mutation versus Reassignment
This is the simplest split to remember.
Mutation:
const updateUser = (user: { name: string }): void => { user.name = 'Ellie';};Reassignment:
const updateUser = (user: { name: string }): void => { user = { name: 'Ellie', };};The first changes the contents of the existing object.
The second only points the local parameter variable at a different object.
So one affects the caller's visible state and the other does not.
Arrays Follow the Same Rule
Arrays are just another version of the same idea.
const addItem = (items: string[]): void => { items.push('new item');};const list = ['a', 'b'];addItem(list);console.log(list);That mutates the original array, so list becomes ['a', 'b', 'new item'].
But:
const replaceItems = (items: string[]): void => { items = ['x', 'y'];};does not replace the caller's array. It only reassigns the local parameter.
Why Beginners Get Surprised
The surprise usually comes from treating objects as if they were copied automatically when passed around.
They are not.
If two variables point at the same object, then mutating through one variable is visible through the other as well.
That is true whether the sharing happened through:
- function arguments
- assignment
- array membership
- object properties
Once you see that shared‑object model clearly, these bugs stop feeling random.
This Matters Because Functions Can Create Hidden Side Effects
If a function mutates an object argument, that function has a side effect visible outside itself.
That is not automatically wrong, but it is something worth being deliberate about.
For example:
const applyDiscount = (order: { total: number }): void => { order.total = order.total * 0.9;};That function silently changes the caller's object. Sometimes that is what you want. Sometimes it is the source of a subtle bug.
If you do not want that side effect, return a new object instead:
const applyDiscount = (order: { total: number }): { total: number } => { return { ...order, total: order.total * 0.9, };};Now the update is explicit and the original object remains untouched.
The Wording Matters Less than the Behaviour
Developers can get very argumentative about the exact terminology here. In practical front‑end work, the key behaviour is what matters:
- primitive reassignment inside a function does not affect the outer value
- object and array mutation inside a function affects the same underlying object or array the caller still sees
- parameter reassignment does not reach back out and replace the caller's variable
If you understand those three rules, you can reason about most of the bugs that this topic causes.
A Quick Reality Check for Debugging
When something seems to "change from nowhere", ask:
- Am I dealing with a primitive or an object‑like value?
- Did this function mutate the object, or only reassign a local variable?
- Are several variables pointing at the same underlying object?
Those questions usually expose the issue very quickly.
Wrapping up
Pass by value versus reference in JavaScript is easiest to understand when you stop asking one big abstract question and instead watch what happens to variables and underlying objects separately. Primitive values behave independently when reassigned. Objects and arrays can be mutated through shared references, which is why changes made inside a function often show up outside it too.
Key Takeaways
- Primitive arguments behave like independent values when reassigned inside a function.
- Objects and arrays can be mutated through a function argument because the same underlying value is still being referenced.
- Reassigning a parameter is not the same as mutating the object it points to.
- Many "mysterious" state changes come from shared object references rather than from JavaScript changing its rules.
- Returning new objects instead of mutating shared ones often makes code easier to reason about.
Once you separate mutation from reassignment, this topic becomes far less slippery than its reputation suggests.
Related Articles
Accessing a Random Element from an Array Using JavaScript. 
Primitive vs. Reference Types in JavaScript. Primitive vs. Reference Types in JavaScript

Mutation vs. Immutability in JavaScript Arrays and Objects. Mutation vs. Immutability in JavaScript Arrays and Objects

The CSS overflow Property. The CSS
overflowProperty
Toggle a Boolean in JavaScript. Toggle a Boolean in JavaScript

Control CSS Container Layouts with place‑content. Control CSS Container Layouts with
place‑content
The will‑change Property in CSS. The
will‑changeProperty in CSS
Image Optimisation with next/image. Image Optimisation with
next/image
Optional Chaining in JavaScript (?.). Optional Chaining in JavaScript (
?.)DOM Traversal: closest() in Vanilla JavaScript and jQuery. DOM Traversal:
closest()in Vanilla JavaScript and jQuery
Common Accessibility Pitfalls in Web Development. Common Accessibility Pitfalls in Web Development

UseReducer in React. useReducerin React