JavaScript is pass-by-value; there is no pass-by-reference. What makes it look otherwise is what an object value is: a reference to an object. MDN states it directly: arguments are always passed by value, and object arguments are passed by sharing.
The spec shows the mechanism. A call is evaluated by ArgumentListEvaluation, which evaluates each argument expression and calls GetValue on it, producing a list of language values. Call then binds those values to the parameters. The callee never receives the caller's variable, only what it held.
- Primitive argument: the parameter gets its own copy of an immutable value. Nothing the callee does can change the caller's variable.
- Object argument: the parameter gets a copy of the reference. Caller variable and parameter now point at the same object.
That gives the rule to say in an interview:
- Mutating the object through the parameter (
user.name = 'Ada',list.push(1)) is visible to the caller, because both references reach the same object. - Reassigning the parameter (
user = {}) only repoints the callee's local binding. The caller's variable is untouched.
True pass-by-reference would let swap(a, b) exchange the caller's variables. In JavaScript it can't, because the function never has access to those bindings.
Assignment works the same way: const alias = obj copies the reference, so alias and obj are one object. The same applies to spread and Object.assign: they copy the references held by the top-level properties (a shallow copy). For a deep copy, use structuredClone, a host API defined by the HTML standard rather than ECMAScript.