On this page · sections
JavaScript Objects, Arrays & Descriptors
Can you control copying, mutation, and property visibility without accidentally sharing nested state?
Objects and arrays are references. Every property also has a descriptor. Interviews mix shallow-copy bugs with defineProperty, enumerability, and mutating array methods.
These questions test:
- Shallow vs deep copy
- Descriptors: writable, enumerable, configurable
- Getters and setters
- Mutating vs copying array APIs
- Prototype pollution on untrusted keys
Questions
Copying
Properties
Senior-Level Follow-ups
Copying
1. References and Shallow Copy
Why This Matters
const copy = { ...state } looks immutable. Nested objects are still shared.
Problem
When does spread isolate mutation, and when does it not?
Example
const original = {
user: { name: "Ada" },
tags: ["js"],
};
const copy = { ...original };
copy.tags.push("ts");
copy.user.name = "Grace";
original.tags; // ["js", "ts"]
original.user.name; // "Grace"The outer object is new. user and tags are the same references.
Object.assign, array.slice(), and [...arr] are the same shallow contract.
Deep isolation is a graph clone — see Deep Clone & Equality.
Interview Answer
“Spread copies the container. Nested objects and arrays stay shared. I only treat a spread as a snapshot when the value is flat, or I clone the nested parts I intend to change.”
2. Mutating vs Non-Mutating Arrays
Why This Matters
React state and Redux both break if you sort or splice the current array in place.
Problem
Which methods mutate, and what do you use instead?
Example
| Mutates | Copying alternative |
|---|---|
push / pop | [...arr, x] / arr.slice(0, -1) |
splice | toSpliced or slice + concat |
sort | toSorted or [...arr].sort() |
reverse | toReversed or [...arr].reverse() |
const next = [...items, item];
const without = items.filter((x) => x.id !== id);Interview Answer
“I treat in-place array methods as unsafe for shared state. I copy first, or I use the non-mutating ES2023 methods. The contract is: the previous reference must still look the same.”
Properties
3. Property Descriptors
Why This Matters
Literal assignment hides four flags. Libraries and Vue 2-style reactivity are built on them.
Problem
What does Object.defineProperty control?
Example
const user = {};
Object.defineProperty(user, "id", {
value: 42,
writable: false,
enumerable: true,
configurable: false,
});
user.id = 99;
// sloppy: ignored; strict: TypeErrorLiteral { a: 1 } defaults:
writable: true
enumerable: true
configurable: trueObject.defineProperty(o, "b", { value: 2 }) defaults the missing flags to false.
Interview Answer
“Every property has a descriptor. Data descriptors have
value/writable. Accessor descriptors haveget/set.configurable: falselocks the descriptor itself. I never assume defineProperty uses the same defaults as an object literal.”
4. Getters, Setters, and Enumerability
Problem
When do getters run, and which keys appear in Object.keys?
Example
const account = {
_balance: 0,
get balance() {
return this._balance;
},
set balance(amount) {
if (amount < 0) throw new Error("negative");
this._balance = amount;
},
};Reading account.balance runs get. JSON.stringify also invokes getters.
Object.keys({ a: 1, [Symbol("s")]: 2 });
// ["a"] — symbols omitted
for (const key in obj) {
// includes enumerable inherited keys
}Object.keys is own + enumerable + string. Object.getOwnPropertyNames includes non-enumerable strings. Object.getOwnPropertySymbols is separate.
Interview Answer
“Getters run on read, including serialization.
Object.keysis own enumerable strings.for...inwalks the enumerable chain. I useObject.hasOwnwhen I only want own keys.”
Senior-Level Follow-ups
5. Object.freeze Is Shallow
const o = { nested: { x: 1 } };
Object.freeze(o);
o.nested.x = 2; // allowedFreeze sets writable: false and configurable: false on own properties. Nested objects need a walk, or you copy instead of freeze.
6. Destructuring Defaults vs Null
const { n = 1 } = { n: 0 };
// n is 0 — default only applies for undefined
const { n = 1 } = { n: null };
// n is nullDefaults do not treat null as missing. Use nullish coalescing when both null and undefined should fall back:
const n = value.n ?? 1;7. Unsafe Keys
Path utilities that assign obj[key] = value must reject:
__proto__
constructor
prototypeOtherwise untrusted JSON can pollute Object.prototype. Use a denylist or Object.create(null) dictionaries. See Array & Object Utilities.
Final Interview Strategy
1. Assignment copies the reference
2. Spread is shallow
3. Do not mutate shared arrays
4. Know descriptor defaults
5. Getters run on read
6. freeze is shallow
7. Reject prototype keys on untrusted inputSenior-level signal: Talk about ownership. Who holds the reference, what a copy actually copies, and which keys you are willing to write.