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)
- Read lessons in order —
README.md, then1.24.a->1.24.h. - Practice out loud — definition -> example -> pitfall.
- Pair with exercises —
1.24-Exercise-Questions.md. - Quick review —
1.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:
- Mutation:
Object.assign(target, src)mutates the target and returns it. Spread always creates a new object. - Setters:
Object.assigntriggers setters on the target object. Spread does not — it defines new properties directly. - Prototype:
Object.assigncan write onto any existing object (including one with a custom prototype). Spread always creates a plainObject.prototypeobject. - Syntax: Spread is syntax (cannot be polyfilled).
Object.assignis 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:
for...in— iterates all enumerable properties including inherited. RequireshasOwnPropertyguard for safety.Object.keys(obj).forEach(key => ...)— own enumerable only. Clean, but nobreak.for (const key of Object.keys(obj))— own enumerable, supportsbreak/continue.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:
| Operation | seal | freeze |
|---|---|---|
| Add property | No | No |
| Delete property | No | No |
| Modify value | Yes | No |
| Reconfigure descriptor | No | No |
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:
structuredClone(obj)— modern, handles circular refs,Date,Map,Set, etc. Best choice.JSON.parse(JSON.stringify(obj))— works for JSON-safe data. Losesundefined, functions,Dateobjects,Infinity,NaN.- 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)returnstrue(whereasNaN === NaNisfalse)Object.is(+0, -0)returnsfalse(whereas+0 === -0istrue)
Most code uses ===. Use Object.is when:
- Comparing values that might be
NaN(avoiding false negatives) - Distinguishing
+0from-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:
- Integer-like keys — sorted in ascending numeric order (
"0","1","2","10") - String keys — in insertion order
- Symbol keys — in insertion order (excluded from
keys/values/entries, but visible viagetOwnPropertySymbols)
"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:
- It invokes the getter (if the source property has one) to get a value
- 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.isinstead of===to handleNaNcorrectly - 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:
- Non-enumerable properties —
entriesskips them - Symbol-keyed properties —
entriesonly returns string keys - Property descriptors — all properties become
writable: true, enumerable: true, configurable: true - Prototype — result always has
Object.prototype, even if original had a custom prototype - 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
| # | Question | One-line answer |
|---|---|---|
| 1 | Object.keys vs for...in | keys = own only; for...in includes inherited |
| 2 | assign vs spread | assign mutates target + triggers setters; spread creates new object |
| 3 | freeze vs seal | freeze = immutable; seal = fixed shape, mutable values |
| 4 | Object.is(NaN, NaN) | true (unlike ===) |
| 5 | entries -> fromEntries | Round-trip creates new object; loses non-enumerable, Symbols, prototype |
| 6 | Deep freeze? | Object.freeze is shallow; write recursive deepFreeze |
| 7 | Empty object check | Object.keys(obj).length === 0 |
| 8 | Object.assign copy depth | Shallow only — nested objects are shared references |
| 9 | Property order | Integer keys sorted, then string keys by insertion order |
| 10 | const vs freeze | const = binding; freeze = object content |
<- Back to 1.24 — Object Methods (README)