In JavaScript, how does assigning a primitive to another variable differ from assigning an object, and what does that mean for a value you pass into a function?
answer
- one rule, two different values
- the handle is what gets copied
- mutate versus rebind
- the callee owns its own binding
- two literals, two identities
basics
~20 sAssignment always copies a value. For a primitive that value is the data itself, so the two variables are independent. For an object the value is a reference, so both names point at one object and a mutation through either is visible through both.
solid answer
~50 sJavaScript always copies the value on assignment and on argument passing — the difference is *what* the value is. For a primitive the value is the data, so `let b = a` gives you an independent copy and nothing you do to `b` touches `a`. For an object the value is a reference to one shared object, so `const p = o` gives you a second name for the same thing; `p.n = 2` is observable through `o`. Inside a function this produces two different outcomes that people often merge: **mutating** the parameter's object (`opts.retries = 5`) is visible to the caller, while **reassigning** the parameter (`opts = {}`) only rebinds the local name and the caller sees nothing. That is why JavaScript is described as pass-by-value, or call-by-sharing — never pass-by-reference in the sense that reassignment would propagate.
code
javascript · 11 linesfunction update(o, n) {
o.count = 5; // mutation: visible to the caller
n = 5; // rebinding: local only
o = { count: 99 }; // rebinding: local only
}
const obj = { count: 1 };
let num = 1;
update(obj, num);
console.log(obj.count, num); // 5 1go deeper
Know that copying a variable that holds an object gives you a second name for the same object, while copying a number or string gives you an independent value. Be able to predict what a small mutation prints.
Explain the single rule — assignment copies the value — and show that for objects that value is a reference. Demonstrate the mutation-versus-reassignment split inside a function without reaching for the phrase "pass by reference".
Show the production shape of this: shared references leaking state between requests, arrays of "snapshots" that are all one object, and a shallow copy that only detached the top level. Say where in the system you copy and why there.
Set the ownership rule for the codebase: which layers may mutate values they receive, where inputs are copied, and how that is enforced. Frame it as a cost decision — copying everywhere is wasteful, copying nowhere makes state impossible to reason about.
## One rule, two outcomes There is only one assignment rule in JavaScript: **the value is copied**. Everything people call "pass by reference" falls out of what the value happens to be. For a primitive, the value *is* the number, string or boolean. For an object, the value is a reference — a handle to one object living somewhere else. Copy a handle and you have two handles to one object; copy a number and you have two numbers. ```js let a = 1; let b = a; // copies the number b = 2; console.log(a); // 1 — untouched const o = { n: 1 }; const p = o; // copies the reference p.n = 2; console.log(o.n); // 2 — one object, two names ``` Note what `const` does and does not promise in the second case: it forbids rebinding `p`, not writing to the object `p` refers to. ## Aliasing: the word for the second case When two names refer to one object they are **aliases**. Aliases are created by far more than plain assignment: pushing an object into an array, storing it in a `Map`, closing over it in a callback, or handing it to a function all copy the reference and produce another alias. Every alias is a write path into the same object, and that is the entire source of "who changed my data?" bugs in JavaScript. ## Function arguments: mutation versus reassignment This is the distinction interviewers actually test, because it separates people who have memorised a slogan from people who understand the mechanism. ```js function update(o, n) { o.count = 5; // mutation — writes through the shared reference n = 5; // rebinds the local parameter only o = { count: 99 }; // rebinds the local parameter only } const obj = { count: 1 }; let num = 1; update(obj, num); console.log(obj.count, num); // 5 1 ``` `obj.count` is 5 because the first line reached through the shared reference. `num` is still 1 because `n = 5` only changed the parameter binding. And the later `o = { count: 99 }` is invisible outside for exactly the same reason — `o` is a local name that happened to start out pointing at the caller's object. This is what "JavaScript is always pass-by-value" means, and why some people prefer the more precise term **call-by-sharing**: the callee shares the object with the caller but holds its own binding. A true pass-by-reference language would let `o = { count: 99 }` replace the caller's variable. JavaScript has no such mechanism. ## The consequences that show up in real code **Shared state that outlives a call.** A function that normalises the options object it was handed writes into the caller's object. If that object is a module-level constant or a value the caller reuses, the mutation persists across calls. **Snapshots that are not snapshots.** `history.push(state)` where `state` is an object stores a reference, not a copy. Later mutations rewrite every "snapshot" in the array at once — all entries were always the same object. **Comparisons that surprise.** `{ a: 1 } === { a: 1 }` is `false` because the two literals produce two distinct objects with distinct references, whereas `'abc' === 'abc'` is `true` because the values are equal. Primitives have no identity separate from their content; objects do. **Copies are shallow by default.** `const copy = { ...original }` copies the top-level entries — but each entry that held a reference still holds *the same* reference, so `copy.nested` and `original.nested` remain aliases. A one-level copy is often enough, but you have to know which level you have detached. ## Defensive habits worth naming - Treat parameters as read-only: build and return a new object rather than writing into the one you were given. - Copy at the boundary where a value enters or leaves your module, not at the twentieth call site inside it. - When storing a caller's object for later, decide explicitly whether you want a live view (keep the reference) or a snapshot (copy) — and write it down. ## How to say it in an interview "Assignment copies the value. For primitives the value is the data; for objects the value is a reference. So mutating through a parameter is visible to the caller and reassigning the parameter is not — JavaScript passes everything by value, and for objects the thing passed by value is the reference."
- Why does `{ a: 1 } === { a: 1 }` evaluate to false while `'abc' === 'abc'` is true?Each object literal evaluates to a newly created object, so the two operands are references to two distinct objects and the comparison of those references is false. Primitives have no identity apart from their content, so two `'abc'` values are simply the same value. This is why deduplicating objects by comparison never works the way people first expect.
- If a copy made with `{ ...original }` is shallow, which parts of the data are still shared?Every value that was itself a reference. Top-level entries are copied, so replacing `copy.name` does not affect `original.name` — but `copy.address` and `original.address` are the same object, so writing `copy.address.city` is visible through both. A shallow copy detaches exactly one level and nothing below it.
- A function receives an array and calls `arr = arr.filter(Boolean)` inside. Why does the caller see no change?`filter` returns a new array and the assignment rebinds only the local parameter, so the caller's variable still refers to the original array. To change what the caller sees you must either mutate the array you were given or, better, return the new array and have the caller assign it.
- How do you decide whether to store a caller's object directly or copy it first?Ask whether you want a live view or a snapshot. Caches, registries and anything read back later usually want a snapshot, because the caller may keep mutating the object they handed you. Short-lived pass-throughs can keep the reference. Decide once at the module boundary and document it, rather than copying defensively at every internal call.
saying these in an interview costs you the question
- Says JavaScript passes objects by reference and primitives by value
- Thinks reassigning a parameter changes the caller's variable
- Believes const on an object prevents property writes
- Assumes spreading an object gives a fully independent copy
- Expects two identical object literals to compare as equal