Q1Write const backup = order;, then run backup.total = 0;. What happens to order.total?
References and Copying — Array and Object Pitfalls
Assigning an array or object copies a reference, not the contents — plus shallow copies, deep copies, and Object.freeze.
Try to back up an order before editing it by writing const backup = order;, and fixing order later changes backup right along with it. This never happens with numbers or strings.
This article covers what actually gets passed when you assign an array or object, and how to build a safe copy.
What Assignment Passes — Primitives vs References
The reason this discrepancy is hard to track down is that what gets passed on assignment differs depending on the kind of value. Here's a rundown of what carries over as-is on assignment, and what doesn't.
The 5 kinds — numbers, strings, booleans, null, and undefined (called primitives — values that are neither objects nor arrays) — get the value itself copied when you assign them.
The 2 variables are unrelated after that copy, so changing one never changes the other.
Arrays and objects are reference types (types that hold information pointing to where a value lives, rather than the value itself). Assignment only passes that location information, so 2 variables end up pointing at the exact same array or object.
Rewrite it through either name, and you're rewriting the same single thing either way.
// A primitive gets its value copied
let price = 3980;
let salePrice = price;
salePrice = 2980;
console.log(price); // 3980 (unaffected)
console.log(salePrice); // 2980
// An array points at the same single thing
const cart = ["A4 Notebook"];
const savedCart = cart;
savedCart.push("Permanent Marker");
console.log(cart.length); // 2 (cart grew too)
console.log(cart === savedCart); // true (they point at the same thing)
Even with const, the Contents Can Be Rewritten
What const fixes is only where the variable points. Write const cart = [];, then cart.push("Permanent Marker"), and the contents grow; const order = {}, then order.total = 0, works too.
What's forbidden is only reassignment that points it somewhere else entirely, like cart = [...].
Building a Copy — What === Actually Looks At
As you've just seen, assignment alone doesn't give you a safe copy separated from the original. Want a separate order with only the amount changed, leaving the original untouched? Build a new object laying out the contents with spread syntax, as in { ...order }. What's worth confirming is the relationship between what you build and the original.
const copiedOrder = { ...order }; builds a separate object, so order === copiedOrder is false. For arrays and objects, === checks not whether the contents match, but whether they're the exact same single thing.
To compare contents, write out the specific things you want to compare yourself — the number of keys, individual values, and so on.
true when they point at the same thing, and false for separately built things, even with identical contents.const cart = ["A4 Notebook"];
const copiedCart = [...cart];
// Separate things, so === is false
console.log(cart === copiedCart); // false
// Even with identical contents, still false
console.log(["A"] === ["A"]); // false
// Compare the contents themselves, and it's true
console.log(cart[0] === copiedCart[0]); // true
// Only true when they point at the same thing
const savedCart = cart;
console.log(cart === savedCart); // true
Copying Doesn't Reach Inside a Nested Value — Shallow vs Deep Copy
The spread syntax so far has made safe copies for primitive values like an amount. But copy an order with a nested object, like customer info, and while the outer object becomes a separate one, the inner one still points at the exact same thing as the original. Fix an address on what you thought was the copy, and the original order's address changes too — a mismatch you didn't expect.
What spread syntax builds is a shallow copy (a copy that only duplicates the first level of properties, leaving any nested object pointing at the same thing). Values at the first level become independent if they're primitives, but when what's inside is an object or array, the location information — the reference — just gets copied over as-is.
When you need the inside rebuilt too, use a deep copy (a copy that rebuilds everything, all the way into nested values). Write structuredClone(order), and every level of nesting gets rebuilt as a separate object.
It's a function the browser provides, so it works directly in this site's console too. It just doesn't work on an object that contains a function.
const order = {
orderId: "ORD-1342",
customer: { name: "Kenji Ross", city: "Denver" },
};
// Shallow copy: the 1st level is separate, the inside still points at the same thing
const shallow = { ...order };
shallow.orderId = "ORD-9999";
console.log(order.orderId); // ORD-1342 (independent)
shallow.customer.city = "Boulder";
console.log(order.customer.city); // Boulder (the original changed too)
console.log(order.customer === shallow.customer); // true
// Deep copy: even the inside becomes separate
const deep = structuredClone(order);
deep.customer.city = "Aurora";
console.log(order.customer.city); // Boulder (the original is safe)
console.log(order.customer === deep.customer); // false
Stopping Rewrites — Object.freeze
As you've seen, arrays and objects can end up mutated unintentionally just by sharing a reference. Some values, like default settings or a tax rate, call for a different safeguard than const — you don't want them rewritten later. const alone doesn't stop the contents from being rewritten, so you need a way to mark the object itself as off-limits to changes.
Write Object.freeze(settings), and that object stops accepting changes, additions, or removals to its properties. Try to assign, add, or delete something after freezing it, and it throws a TypeError right there, stopping execution.
Check whether something's frozen with Object.isFrozen(settings). Freezing only reaches the first level, so a nested object needs to be frozen separately.
const settings = { theme: "dark" };
// Rewritable before freezing
settings.theme = "light";
console.log(settings.theme); // light
Object.freeze(settings);
console.log(Object.isFrozen(settings)); // true
// Assigning after freezing stops on that line
// settings.theme = "dark";
// TypeError: Cannot assign to read only property 'theme' of object '#<Object>'
// Freezing only reaches the 1st level — the inside doesn't stop
const nested = Object.freeze({ display: { theme: "dark" } });
nested.display.theme = "light";
console.log(nested.display.theme); // light
// To change it, build a new object instead
const updated = { ...settings, theme: "dark" };
console.log(updated.theme); // dark
console.log(settings.theme); // light (the original is safe)
To Change It, Build a New Object
Assigning, adding, or deleting on a frozen object throws a TypeError, and nothing after that line runs. To change a frozen object, don't rewrite it — build a new one instead, as in { ...settings, theme: "light" }.
Knowledge Check
Answer each question one by one.
Q2With const a = ["A"]; const b = ["A"];, what does a === b return?
Q3Copy data with a nested object using { ...order }. What happens to the nested object?