Topics
v6.0.0
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

  1. References and Shallow Copy
  2. Mutating vs Non-Mutating Arrays

Properties

  1. Property Descriptors
  2. Getters, Setters, and Enumerability

Senior-Level Follow-ups

  1. Object.freeze Is Shallow
  2. Destructuring Defaults vs Null
  3. Unsafe Keys

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

MutatesCopying alternative
push / pop[...arr, x] / arr.slice(0, -1)
splicetoSpliced or slice + concat
sorttoSorted or [...arr].sort()
reversetoReversed 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: TypeError

Literal { a: 1 } defaults:

writable: true enumerable: true configurable: true

Object.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 have get/set. configurable: false locks 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.keys is own enumerable strings. for...in walks the enumerable chain. I use Object.hasOwn when 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; // allowed

Freeze 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 null

Defaults 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 prototype

Otherwise 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 input

Senior-level signal: Talk about ownership. Who holds the reference, what a copy actually copies, and which keys you are willing to write.

Last updated on