Episode 1 — Fundamentals / 1.23 — Objects
Interview Questions: JavaScript Objects
Model answers for object fundamentals, reference vs value, property access, mutation, deep/shallow copy, iteration, and common patterns.
How to use this material (instructions)
- Read lessons in order —
README.md, then1.23.a→1.23.f. - Practice out loud — definition → example → pitfall.
- Pair with exercises —
1.23-Exercise-Questions.md. - Quick review —
1.23-Quick-Revision.md.
Beginner (Q1–Q6)
Q1. What is a JavaScript object?
Why interviewers ask: Confirms foundational understanding of JS data structures.
Model answer:
A JavaScript object is a collection of key-value pairs (properties) where keys are strings (or Symbols) and values can be any type. Objects are reference types — variables hold a reference to the object in memory, not the object itself. They are created most commonly with object literal syntax { key: value } and are the primary tool for grouping related data.
const user = { name: "Alice", age: 25, isAdmin: true };
Q2. What is the difference between dot notation and bracket notation?
Why interviewers ask: Tests daily coding fluency and understanding of dynamic access.
Model answer:
Dot notation (obj.key) is cleaner and preferred for known, valid identifier keys. Bracket notation (obj["key"]) is required when the key is dynamic (stored in a variable), contains special characters, spaces, or starts with a digit.
const user = { name: "Alice", "full name": "Alice Johnson" };
user.name; // dot — clean
user["full name"]; // bracket — required (space in key)
const key = "name";
user[key]; // bracket — required (dynamic key)
Q3. Explain the difference between primitives and objects (reference types).
Why interviewers ask: This underlies mutation bugs, equality checks, and function parameter behavior.
Model answer:
Primitives (string, number, boolean, null, undefined, symbol, bigint) are stored by value — copying creates an independent duplicate. Objects (including arrays and functions) are stored by reference — copying a variable copies the reference, so both variables point to the same data in memory.
// Primitive — independent copies
let a = 5;
let b = a;
b = 10;
console.log(a); // 5
// Object — shared reference
let obj1 = { x: 1 };
let obj2 = obj1;
obj2.x = 99;
console.log(obj1.x); // 99 — same object!
This is why {} === {} is false — they are two different objects in memory, even though they look identical.
Q4. What is optional chaining and when do you use it?
Why interviewers ask: Modern syntax awareness and safe coding practices.
Model answer:
Optional chaining (?.) short-circuits to undefined if the value to the left is null or undefined, instead of throwing a TypeError. It is used for safe access on nested data where intermediate properties might be missing.
const user = { name: "Alice" };
// Without — crashes if address is undefined
// user.address.city; // TypeError
// With — returns undefined safely
user.address?.city; // undefined
// With methods
user.greet?.(); // undefined (no error)
// With bracket notation
user.settings?.["theme"]; // undefined
Combine with ?? for defaults: user.address?.city ?? "Unknown".
Q5. What is the difference between delete obj.prop and obj.prop = undefined?
Why interviewers ask: Tests precise understanding of property existence vs value.
Model answer:
delete obj.prop completely removes the property from the object — it will no longer appear in Object.keys, for...in, or "prop" in obj.
obj.prop = undefined sets the value to undefined but the property still exists — it appears in Object.keys and "prop" in obj returns true.
const obj = { a: 1, b: 2 };
obj.a = undefined;
delete obj.b;
"a" in obj; // true — property exists, value is undefined
"b" in obj; // false — property removed
Object.keys(obj); // ["a"]
Q6. How do you check if a property exists on an object?
Why interviewers ask: Distinguishing own vs inherited properties.
Model answer:
Three approaches:
"key" in obj— checks own properties AND the prototype chain.obj.hasOwnProperty("key")— checks own properties only, but fails onObject.create(null).Object.hasOwn(obj, "key")(ES2022) — checks own properties only, works on all objects. Preferred.
const obj = { name: "Alice" };
"name" in obj; // true
"toString" in obj; // true (inherited)
Object.hasOwn(obj, "name"); // true
Object.hasOwn(obj, "toString"); // false (not own)
Intermediate (Q7–Q12)
Q7. Explain shallow copy vs deep copy.
Why interviewers ask: Critical for state management, React, and avoiding mutation bugs.
Model answer:
A shallow copy creates a new top-level object, but nested objects and arrays are shared by reference with the original. Modifying a nested property in the copy also changes the original.
A deep copy creates completely independent objects at every level — no shared references.
const original = { name: "Alice", address: { city: "Portland" } };
// Shallow copy — nested address is shared
const shallow = { ...original };
shallow.address.city = "Seattle";
console.log(original.address.city); // "Seattle" — affected!
// Deep copy — fully independent
const deep = structuredClone(original);
deep.address.city = "Denver";
console.log(original.address.city); // "Seattle" — not affected
Shallow: { ...obj }, Object.assign({}, obj).
Deep: structuredClone(obj) (modern), JSON.parse(JSON.stringify(obj)) (legacy, with limitations).
Q8. What are the limitations of JSON.parse(JSON.stringify(obj)) for deep copying?
Why interviewers ask: Tests awareness of serialization gotchas.
Model answer:
The JSON round-trip fails or loses data for several types:
| Type | What happens |
|---|---|
Date | Becomes a string (not restored as Date) |
undefined | Removed from objects |
NaN, Infinity | Become null |
RegExp | Becomes {} |
Map, Set | Become {} |
| Functions | Removed silently |
| Circular references | Throws TypeError |
structuredClone handles Date, Map, Set, NaN, Infinity, and circular references correctly, but still cannot clone functions or DOM nodes.
Q9. What is the property enumeration order in JavaScript objects?
Why interviewers ask: Catches assumptions about key ordering.
Model answer:
Since ES2015, JavaScript objects have a defined enumeration order:
- Integer-like string keys (e.g.,
"0","1","42") — sorted numerically ascending. - Other string keys — in insertion order.
- Symbol keys — in insertion order (only via
getOwnPropertySymbols).
const obj = { b: 1, a: 2, 2: "x", 1: "y" };
Object.keys(obj); // ["1", "2", "b", "a"]
This means if you use numeric-like keys (like HTTP status codes), they will not maintain insertion order. Use Map if insertion order matters for all key types.
Q10. Compare for...in, Object.keys, and Object.entries.
Why interviewers ask: Tests practical iteration knowledge and prototype awareness.
Model answer:
for...in | Object.keys | Object.entries | |
|---|---|---|---|
| Scope | Own + prototype | Own only | Own only |
| Returns | Keys (one per iteration) | Array of keys | Array of [key, value] |
| Array methods | No | Yes (.map, .filter) | Yes |
| Recommendation | Avoid (or guard) | Good for keys only | Best for key+value |
// Preferred modern pattern:
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
Q11. What is this inside an object method?
Why interviewers ask: One of the most confusing and frequently tested JS concepts.
Model answer:
Inside a method called with dot notation (obj.method()), this refers to the object before the dot. However, this is determined by how the function is called, not where it is defined:
const user = {
name: "Alice",
greet() { return this.name; },
};
user.greet(); // "Alice" — this === user
const fn = user.greet;
fn(); // undefined — this is global/undefined (lost context)
Arrow functions do not bind their own this — they inherit from the enclosing lexical scope. This makes them unsuitable as object methods.
Q12. How do you merge two objects?
Why interviewers ask: Practical pattern used in configuration, state management, and API handling.
Model answer:
Two main approaches:
Spread operator (preferred):
const merged = { ...obj1, ...obj2 }; // obj2 values override obj1
- Creates a new object (immutable).
- Last spread wins for duplicate keys.
Object.assign:
Object.assign(target, source1, source2); // mutates target
const merged = Object.assign({}, obj1, obj2); // non-mutating version
- Mutates the first argument.
- Triggers setters on the target (unlike spread).
Both are shallow — nested objects are still shared.
Advanced (Q13–Q18)
Q13. How does Object.create(null) differ from {}?
Why interviewers ask: Tests prototype knowledge and edge cases.
Model answer:
Object.create(null) creates an object with no prototype — it does not inherit toString, hasOwnProperty, or any Object.prototype methods. It is a truly "bare" dictionary.
const bare = Object.create(null);
bare.key = "value";
bare.toString; // undefined (no prototype methods)
bare.hasOwnProperty; // undefined
// This is why Object.hasOwn is safer than obj.hasOwnProperty:
Object.hasOwn(bare, "key"); // true — works on any object
This pattern is used in scenarios like caches or lookup maps where you want zero prototype pollution risk.
Q14. Explain Object.freeze, Object.seal, and Object.preventExtensions.
Why interviewers ask: Immutability patterns and object protection.
Model answer:
| Method | Add new? | Delete? | Modify existing? |
|---|---|---|---|
Object.preventExtensions(obj) | No | Yes | Yes |
Object.seal(obj) | No | No | Yes |
Object.freeze(obj) | No | No | No |
All three are shallow — nested objects are not affected:
const obj = Object.freeze({ nested: { x: 1 } });
// obj.y = 2; // TypeError in strict mode (silently fails otherwise)
obj.nested.x = 99; // Works! Nested object is not frozen
For deep freeze, you would need a recursive solution or library.
Q15. What are property descriptors?
Why interviewers ask: Understanding defineProperty, library internals, and computed properties.
Model answer:
Every property has a descriptor with attributes:
value— the property's value.writable— can the value be changed?enumerable— does it appear infor...in/Object.keys?configurable— can the descriptor be changed or property deleted?
const obj = { name: "Alice" };
console.log(Object.getOwnPropertyDescriptor(obj, "name"));
// { value: "Alice", writable: true, enumerable: true, configurable: true }
Object.defineProperty(obj, "id", {
value: 42,
writable: false,
enumerable: false,
configurable: false,
});
obj.id = 99; // Silently fails (or TypeError in strict mode)
console.log(obj.id); // 42
Object.keys(obj); // ["name"] — "id" is non-enumerable
Q16. What are getters and setters in objects?
Why interviewers ask: Tests understanding of computed properties and encapsulation.
Model answer:
Getters (get) and setters (set) are special methods that look like properties when accessed:
const user = {
firstName: "Alice",
lastName: "Johnson",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
const [first, last] = value.split(" ");
this.firstName = first;
this.lastName = last;
},
};
console.log(user.fullName); // "Alice Johnson" (getter called)
user.fullName = "Bob Smith"; // setter called
console.log(user.firstName); // "Bob"
They enable computed values, validation, and encapsulation without changing the access syntax.
Q17. How would you implement a simple deepEqual function?
Why interviewers ask: Tests recursion, type checking, and edge case handling.
Model answer:
function deepEqual(a, b) {
// Identical references or primitives
if (a === b) return true;
// If either is null/not-object, they are not equal (already checked ===)
if (a === null || b === null) return false;
if (typeof a !== "object" || typeof b !== "object") return false;
// Check array consistency
if (Array.isArray(a) !== Array.isArray(b)) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(key => Object.hasOwn(b, key) && deepEqual(a[key], b[key]));
}
deepEqual({ a: { b: 1 } }, { a: { b: 1 } }); // true
deepEqual({ a: 1 }, { a: 2 }); // false
deepEqual([1, 2], [1, 2]); // true
This handles nested objects and arrays but not Date, RegExp, Map, Set, or circular references (production code should use a library like Lodash _.isEqual).
Q18. What is the difference between Object.keys, Object.getOwnPropertyNames, and Reflect.ownKeys?
Why interviewers ask: Deep understanding of property enumeration and meta-programming.
Model answer:
| Method | Includes non-enumerable? | Includes Symbols? |
|---|---|---|
Object.keys(obj) | No | No |
Object.getOwnPropertyNames(obj) | Yes | No |
Reflect.ownKeys(obj) | Yes | Yes |
const obj = {};
Object.defineProperty(obj, "hidden", { value: 1, enumerable: false });
obj[Symbol("sym")] = 2;
obj.visible = 3;
Object.keys(obj); // ["visible"]
Object.getOwnPropertyNames(obj); // ["hidden", "visible"]
Reflect.ownKeys(obj); // ["hidden", "visible", Symbol(sym)]
Quick-fire
| # | Question | One-line answer |
|---|---|---|
| 1 | typeof [] | "object" — arrays are objects |
| 2 | {} === {} | false — different references |
| 3 | Shallow copy syntax | { ...obj } or Object.assign({}, obj) |
| 4 | Deep copy (modern) | structuredClone(obj) |
| 5 | Check own property (ES2022) | Object.hasOwn(obj, "key") |
| 6 | Safe nested access | obj?.nested?.prop |
| 7 | Default for nullish | value ?? "default" |
| 8 | Iterate key+value | Object.entries(obj) |
| 9 | Remove property immutably | const { prop, ...rest } = obj |
| 10 | Freeze an object | Object.freeze(obj) (shallow only) |
← Back to 1.23 — JavaScript Objects (README)