Episode 1 — Fundamentals / 1.24 — Object Methods

Interview Questions: Object Methods

Model answers for Object.keys/values/entries, Object.assign vs spread, freeze vs seal, Object.fromEntries pipelines, and Object.is edge cases.

How to use this material (instructions)

  1. Read lessons in orderREADME.md, then 1.24.a -> 1.24.h.
  2. Practice out loud — definition -> example -> pitfall.
  3. Pair with exercises1.24-Exercise-Questions.md.
  4. Quick review1.24-Quick-Revision.md.

Beginner (Q1-Q5)

Q1. What do Object.keys(), Object.values(), and Object.entries() return?

Why interviewers ask: Tests understanding of the three core iteration methods and their relationship.

Model answer:

All three return arrays based on an object's own enumerable string-keyed properties, in the same order (integer keys sorted first, then string keys by insertion order).

  • Object.keys(obj) returns an array of property names (strings): ["name", "age"]
  • Object.values(obj) returns an array of property values: ["Alice", 30]
  • Object.entries(obj) returns an array of [key, value] pairs: [["name", "Alice"], ["age", 30]]

None of them include inherited properties, non-enumerable properties, or Symbol-keyed properties.


Q2. What is the difference between Object.assign() and the spread operator?

Why interviewers ask: Daily pattern choice in React/Node codebases.

Model answer:

Both Object.assign({}, src) and { ...src } create a shallow copy of an object. Key differences:

  1. Mutation: Object.assign(target, src) mutates the target and returns it. Spread always creates a new object.
  2. Setters: Object.assign triggers setters on the target object. Spread does not — it defines new properties directly.
  3. Prototype: Object.assign can write onto any existing object (including one with a custom prototype). Spread always creates a plain Object.prototype object.
  4. Syntax: Spread is syntax (cannot be polyfilled). Object.assign is a function (polyfillable).

For immutable patterns (React state), spread is preferred. For mutating an existing object, use Object.assign.


Q3. What does Object.freeze() do? How is it different from const?

Why interviewers ask: Tests understanding of immutability vs variable binding.

Model answer:

const prevents reassignment of the variable — you cannot point the name at a different value. But the object the variable points to is still fully mutable.

Object.freeze(obj) prevents mutation of the object — you cannot add, delete, or change properties. But the variable can still be reassigned (unless it is also const).

const obj = { a: 1 };
obj.a = 2;           // allowed (const does not prevent mutation)

const frozen = Object.freeze({ a: 1 });
frozen.a = 2;        // blocked (freeze prevents mutation)

Use both together for true immutability: const CONFIG = Object.freeze({...}).

Important caveat: Object.freeze is shallow — nested objects must be frozen recursively.


Q4. How do you iterate over an object's properties?

Why interviewers ask: Tests knowledge of multiple approaches and their trade-offs.

Model answer:

There are several approaches:

  1. for...in — iterates all enumerable properties including inherited. Requires hasOwnProperty guard for safety.
  2. Object.keys(obj).forEach(key => ...) — own enumerable only. Clean, but no break.
  3. for (const key of Object.keys(obj)) — own enumerable, supports break/continue.
  4. for (const [key, value] of Object.entries(obj)) — own enumerable, gives both key and value via destructuring. Preferred when you need both.

The Object.entries + destructuring pattern is the most modern and readable approach for most cases.


Q5. What does Object.fromEntries() do?

Why interviewers ask: Tests understanding of the entries/fromEntries pipeline pattern.

Model answer:

Object.fromEntries(iterable) is the inverse of Object.entries(). It takes any iterable of [key, value] pairs and builds a plain object from them.

Common sources: arrays of pairs, Map objects, URLSearchParams, generators.

The most powerful pattern is the pipeline: Object.entries(obj) -> array methods (map, filter) -> Object.fromEntries(). This lets you functionally transform objects:

// Filter properties by value
const positive = Object.fromEntries(
  Object.entries(obj).filter(([_, v]) => v > 0)
);

Intermediate (Q6-Q10)

Q6. Explain the difference between Object.seal() and Object.freeze().

Why interviewers ask: Tests nuanced understanding of object immutability levels.

Model answer:

Both prevent adding and deleting properties. The key difference is value modification:

Operationsealfreeze
Add propertyNoNo
Delete propertyNoNo
Modify valueYesNo
Reconfigure descriptorNoNo

freeze implies seal — every frozen object is sealed, but not vice versa.

Use seal for fixed-shape objects that need updates (form data, game entities). Use freeze for true constants (config, enums).

Both are shallow — nested objects are not affected unless you write a deep version.


Q7. Why is Object.assign a shallow copy? How do you deep clone?

Why interviewers ask: Frequent source of bugs in real applications.

Model answer:

Object.assign copies property values. If a value is a primitive, it is copied by value (independent). If a value is an object (array, nested object, etc.), it is copied by reference — both the original and clone point to the same nested object.

const original = { data: { count: 1 } };
const clone = Object.assign({}, original);
clone.data.count = 99;
console.log(original.data.count); // 99 -- shared reference!

Deep clone options:

  1. structuredClone(obj) — modern, handles circular refs, Date, Map, Set, etc. Best choice.
  2. JSON.parse(JSON.stringify(obj)) — works for JSON-safe data. Loses undefined, functions, Date objects, Infinity, NaN.
  3. Libraries — Lodash _.cloneDeep, or manual recursive cloning.

Q8. What is Object.is() and when would you use it?

Why interviewers ask: Edge-case knowledge + React internals awareness.

Model answer:

Object.is(a, b) is a same-value equality check. It behaves like === except:

  • Object.is(NaN, NaN) returns true (whereas NaN === NaN is false)
  • Object.is(+0, -0) returns false (whereas +0 === -0 is true)

Most code uses ===. Use Object.is when:

  • Comparing values that might be NaN (avoiding false negatives)
  • Distinguishing +0 from -0 (rare)

React uses Object.is internally for useState and useEffect dependency comparison. This prevents infinite re-renders when state is NaN (since Object.is(NaN, NaN) correctly returns true).


Q9. Explain property enumeration order in JavaScript objects.

Why interviewers ask: Tests deep understanding of object internals.

Model answer:

Since ES2015, property order is deterministic for Object.keys, Object.values, Object.entries, and for...in:

  1. Integer-like keys — sorted in ascending numeric order ("0", "1", "2", "10")
  2. String keys — in insertion order
  3. Symbol keys — in insertion order (excluded from keys/values/entries, but visible via getOwnPropertySymbols)

"Integer-like" means the string converts to a non-negative integer less than 2^32 - 1 and back to the same string.

Object.keys({ b: 1, 2: 2, a: 3, 1: 4 });
// ["1", "2", "b", "a"]

Q10. How do you filter or transform an object's properties?

Why interviewers ask: Practical functional programming with objects.

Model answer:

The standard pattern is the entries pipeline:

// Filter: keep only properties where value > 0
const filtered = Object.fromEntries(
  Object.entries(obj).filter(([key, val]) => val > 0)
);

// Transform: uppercase all string values
const transformed = Object.fromEntries(
  Object.entries(obj).map(([key, val]) =>
    [key, typeof val === "string" ? val.toUpperCase() : val]
  )
);

// Pick specific keys
const picked = Object.fromEntries(
  Object.entries(obj).filter(([key]) => ["name", "email"].includes(key))
);

This is the object equivalent of Array.prototype.filter/map. Before Object.fromEntries (ES2019), developers used reduce to build the result object.


Advanced (Q11-Q15)

Q11. How does Object.assign handle getters, setters, and property descriptors?

Why interviewers ask: Deep understanding of property copying semantics.

Model answer:

Object.assign does not copy property descriptors. For each source property:

  1. It invokes the getter (if the source property has one) to get a value
  2. It calls the setter on the target (if one exists for that key), or defines a data property

This means:

  • Source getters are called, and their return value is assigned as a plain data property
  • Target setters are triggered during assignment
  • Non-writable target properties cause a TypeError (assign stops, already-copied props remain)

To preserve descriptors, use Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)).


Q12. Implement a shallowEqual function that correctly handles NaN.

Why interviewers ask: Tests Object.is + Object.keys combined understanding. React's internal comparison.

Model answer:

function shallowEqual(objA, objB) {
  if (Object.is(objA, objB)) return true;
  if (typeof objA !== "object" || typeof objB !== "object") return false;
  if (objA === null || objB === null) return false;

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) return false;

  return keysA.every(key =>
    Object.hasOwn(objB, key) && Object.is(objA[key], objB[key])
  );
}

Key points:

  • Uses Object.is instead of === to handle NaN correctly
  • Checks key count before iterating
  • Verifies keys exist on objB (not inherited)
  • This is essentially what React uses internally

Q13. What are the limitations of Object.entries/fromEntries round-tripping?

Why interviewers ask: Deep understanding of property descriptors and prototypes.

Model answer:

Object.fromEntries(Object.entries(obj)) creates a new plain object but loses:

  1. Non-enumerable propertiesentries skips them
  2. Symbol-keyed propertiesentries only returns string keys
  3. Property descriptors — all properties become writable: true, enumerable: true, configurable: true
  4. Prototype — result always has Object.prototype, even if original had a custom prototype
  5. Getters/setters — getter is invoked, value is stored as data property

For a true clone that preserves everything:

const clone = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

Q14. Explain the relationship between freeze, seal, and preventExtensions.

Why interviewers ask: Tests understanding of the immutability hierarchy.

Model answer:

These form a strict hierarchy:

preventExtensions  <  seal  <  freeze
(least restrictive)         (most restrictive)

Object.preventExtensions(obj):

  • Cannot add new properties
  • CAN delete properties, modify values, reconfigure descriptors

Object.seal(obj) = preventExtensions + make all properties non-configurable:

  • Cannot add or delete properties
  • Cannot reconfigure descriptors
  • CAN modify values (if writable)

Object.freeze(obj) = seal + make all properties non-writable:

  • Cannot add, delete, or modify anything

Each level implies the previous: isFrozen(obj) implies isSealed(obj) implies !isExtensible(obj).

All three are shallow and affect only the top level.


Q15. How would you implement a type-safe pick and omit utility?

Why interviewers ask: Practical utility function design using Object methods.

Model answer:

// Pick: keep only specified keys
function pick(obj, keys) {
  return Object.fromEntries(
    keys
      .filter(key => key in obj)
      .map(key => [key, obj[key]])
  );
}

// Omit: exclude specified keys
function omit(obj, keys) {
  const keySet = new Set(keys);
  return Object.fromEntries(
    Object.entries(obj).filter(([key]) => !keySet.has(key))
  );
}

The omit version uses a Set for O(1) lookups instead of Array.includes (O(n)) — important for large exclusion lists.

The pick version iterates the keys array (usually small) rather than Object.entries (could be large), making it efficient when picking few keys from large objects.


Quick-fire

#QuestionOne-line answer
1Object.keys vs for...inkeys = own only; for...in includes inherited
2assign vs spreadassign mutates target + triggers setters; spread creates new object
3freeze vs sealfreeze = immutable; seal = fixed shape, mutable values
4Object.is(NaN, NaN)true (unlike ===)
5entries -> fromEntriesRound-trip creates new object; loses non-enumerable, Symbols, prototype
6Deep freeze?Object.freeze is shallow; write recursive deepFreeze
7Empty object checkObject.keys(obj).length === 0
8Object.assign copy depthShallow only — nested objects are shared references
9Property orderInteger keys sorted, then string keys by insertion order
10const vs freezeconst = binding; freeze = object content

<- Back to 1.24 — Object Methods (README)