JavaScript Objects
Master JavaScript objects for interviews — literals, destructuring, cloning, Map/Set, descriptors, utility patterns, and practice problems with solutions.
Introduction
Objects are the backbone of JavaScript — configuration, API payloads, React props, and normalized state all live here. This guide walks through fundamentals, senior-level patterns, and practice problems with detailed solutions.
Course Reference
Quick index
1. Creation & literals
Object literal
const user = {
id: 'u1',
name: 'Alice',
role: 'admin',
'full name': 'Alice Smith', // quoted key when needed
isActive: true,
}new Object() constructor
const user = new Object()
user.id = 'u1'
user.name = 'Alice'
// Prefer literals — shorter and idiomatic
const better = { id: 'u1', name: 'Alice' }Computed property names
const field = 'status'
const record = { [field]: 'active' } // { status: 'active' }
const key = 'userId'
const dynamic = {
[key]: 42,
[`${key}_label`]: 'User ID',
}Method shorthand
const cart = {
items: {},
add(id) {
this.items[id] = (this.items[id] ?? 0) + 1
},
total() {
return Object.values(this.items).reduce((sum, qty) => sum + qty, 0)
},
}2. Property access & optional chaining
Dot vs bracket notation
const user = { name: 'Alice', role: 'admin' }
user.name // 'Alice' — dot for known keys
user['role'] // 'admin' — bracket for dynamic keys
const key = 'name'
user[key] // 'Alice'Add, update, delete
const product = { id: 1, name: 'Mug' }
product.price = 12.99 // add / update
product['inStock'] = true
delete product.inStock // removes own propertyOptional chaining (?.)
const user = { profile: { city: 'NYC' } }
user.profile?.city // 'NYC'
user.address?.city // undefined — no error
user.address?.city ?? 'Remote' // 'Remote'
user.getName?.() // safe method callNullish coalescing (??)
const settings = { theme: null, fontSize: 0 }
settings.theme ?? 'light' // 'light' — null/undefined only
settings.fontSize ?? 14 // 0 — 0 is valid, not replacedInterview trap:
const obj = { a: undefined }
obj.a ?? 'default' // 'default'
'a' in obj // true — key exists even if value is undefined!3. Object.keys, values, entries
const scores = { alice: 90, bob: 75, carol: 88 }
Object.keys(scores) // ['alice', 'bob', 'carol']
Object.values(scores) // [90, 75, 88]
Object.entries(scores) // [['alice', 90], ['bob', 75], ['carol', 88]]Object.fromEntries
const pairs = [
['x', 1],
['y', 2],
]
Object.fromEntries(pairs) // { x: 1, y: 2 }
// Transform object immutably
const curved = Object.fromEntries(Object.entries(scores).map(([name, score]) => [name, score + 5]))Iterate objects
// Preferred — own enumerable string keys
for (const [name, score] of Object.entries(scores)) {
console.log(name, score)
}
// for...in — includes inherited enumerable keys (usually avoid)
for (const key in scores) {
if (Object.hasOwn(scores, key)) {
console.log(key, scores[key])
}
}4. Spread & Object.assign
Merge objects
const defaults = { theme: 'light', pageSize: 10 }
const userPrefs = { pageSize: 25, lang: 'en' }
const merged = { ...defaults, ...userPrefs }
// { theme: 'light', pageSize: 25, lang: 'en' } — later keys winObject.assign
const target = {}
Object.assign(target, defaults, userPrefs)
// mutates target, returns targetImmutable single-field update
const user = { id: 1, name: 'Alice', role: 'viewer' }
const promoted = { ...user, role: 'admin' }
// Nested update (Redux-style)
const state = { cart: { p1: { qty: 1 } }, user: { name: 'Alice' } }
const next = {
...state,
cart: {
...state.cart,
p1: { ...state.cart.p1, qty: 2 },
},
}5. Object destructuring
Basic, rename, default
const employee = {
id: 'e1',
name: 'Alice',
department: 'Eng',
salary: 90000,
}
const { name, department } = employee
const { name: employeeName } = employee
const { role = 'viewer' } = employee // default when missingRest — omit pattern
const { id, ...rest } = employee
// rest = { name, department, salary } — id omittedNested destructuring
const data = { user: { profile: { city: 'NYC', zip: '10001' } } }
const {
user: {
profile: { city },
},
} = dataFunction parameters
function renderRow({ name, salary, status = 'active' }) {
return `${name} (${status}): $${salary}`
}
renderRow({ name: 'Alice', salary: 90000 })Swap object properties
let a = { x: 1 }
let b = { x: 2 }
;[a, b] = [b, a]6. Shallow vs deep copy
const original = { a: 1, nested: { b: 2 }, tags: ['js'] }
// Shallow — top-level new reference, nested shared
const shallow = { ...original }
shallow.nested.b = 99
original.nested.b // 99 — mutated!
// Deep — modern built-in
const deep = structuredClone(original)
deep.nested.b = 0
original.nested.b // 99 — unchanged
// JSON trick — loses Date, Map, undefined, functions, Symbol
const jsonCopy = JSON.parse(JSON.stringify(original))| Method | Depth | Handles Date / Map / functions |
|---|---|---|
spread / Object.assign | Shallow | N/A |
structuredClone | Deep | Yes (most built-in types) |
JSON.parse/stringify | Deep | No |
7. Object.is, hasOwn, in
Object.is — SameValue equality
Object.is(NaN, NaN) // true (NaN === NaN is false)
Object.is(-0, +0) // false (=== treats them as equal)in vs Object.hasOwn
const user = { name: 'Alice' }
'name' in user // true
'toString' in user // true — inherited from prototype!
Object.hasOwn(user, 'name') // true — own property only
Object.hasOwn(user, 'toString') // falseLegacy hasOwnProperty
user.hasOwnProperty('name') // true — can be overridden on object
// Prefer Object.hasOwn (ES2022)8. Descriptors, freeze, seal
Object.defineProperty
const user = {}
Object.defineProperty(user, 'id', {
value: 1,
writable: false,
enumerable: true,
configurable: false,
})
user.id = 2 // fails silently (strict mode: TypeError)freeze, seal, preventExtensions
const obj = { name: 'Alice', meta: { age: 30 } }
Object.freeze(obj) // no add / delete / change own props (shallow)
Object.seal(obj) // can change values, not add/delete keys
Object.preventExtensions(obj) // no new keys, can delete/change existing
obj.meta.age = 31 // still works after freeze — nested not frozen!9. Object.create & prototypes
Create with explicit prototype
const animal = {
speak() {
return `${this.name} speaks`
},
}
const dog = Object.create(animal)
dog.name = 'Rex'
dog.speak() // 'Rex speaks'Clone with property descriptors
const src = {
get label() {
return 'src'
},
}
const clone = Object.create(Object.getPrototypeOf(src), Object.getOwnPropertyDescriptors(src))10. Map & Set
Map — any key type, insertion order
const cache = new Map()
cache.set('user:1', { name: 'Alice' })
cache.set(42, 'answer')
cache.set({ id: 1 }, 'metadata')
cache.get('user:1') // { name: 'Alice' }
cache.has(42) // true
cache.size // 3
cache.delete(42)
for (const [key, value] of cache) {
console.log(key, value)
}Object → Map → Object
const obj = { a: 1, b: 2 }
const map = new Map(Object.entries(obj))
const back = Object.fromEntries(map)Set — unique values
const ids = [1, 2, 2, 3, 3, 3]
const unique = [...new Set(ids)] // [1, 2, 3]Object | Map | Set | |
|---|---|---|---|
| Keys | string / symbol | any | unique values |
| Size | manual count | .size | .size |
| Default | has prototype | clean | clean |
| JSON | serializable | not directly | not directly |
11. Utility patterns
Pick & omit
function pick(obj, keys) {
return Object.fromEntries(keys.filter((k) => k in obj).map((k) => [k, obj[k]]))
}
function omit(obj, keys) {
const exclude = new Set(keys)
return Object.fromEntries(Object.entries(obj).filter(([k]) => !exclude.has(k)))
}
const user = { id: 1, name: 'Alice', password: 'secret' }
pick(user, ['id', 'name']) // safe public fields
omit(user, ['password']) // strip sensitive fieldsShallow equal
function shallowEqual(a, b) {
if (Object.is(a, b)) return true
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length) return false
return keysA.every((key) => Object.is(a[key], b[key]))
}Normalize array → object by id
const products = [
{ id: 'p1', name: 'Mug' },
{ id: 'p2', name: 'Book' },
]
const byId = Object.fromEntries(products.map((p) => [p.id, p]))
// { p1: { id: 'p1', name: 'Mug' }, p2: { ... } }
// Or with reduce
const byId2 = products.reduce((acc, p) => {
acc[p.id] = p
return acc
}, {})Object.groupBy (ES2024)
const employees = [
{ name: 'A', dept: 'Eng' },
{ name: 'B', dept: 'Sales' },
{ name: 'C', dept: 'Eng' },
]
const byDept = Object.groupBy(employees, (e) => e.dept)
// { Eng: [...], Sales: [...] }Deep merge (one level vs recursive)
function deepMerge(target, source) {
const out = { ...target }
for (const [key, value] of Object.entries(source)) {
if (value && typeof value === 'object' && !Array.isArray(value) && target[key] && typeof target[key] === 'object') {
out[key] = deepMerge(target[key], value)
} else {
out[key] = value
}
}
return out
}Quick reference
| Task | API |
|---|---|
| List keys | Object.keys |
| List values | Object.values |
| Key-value pairs | Object.entries |
| Pairs → object | Object.fromEntries |
| Merge | spread, Object.assign |
| Clone shallow | { ...obj } |
| Clone deep | structuredClone |
| Own property check | Object.hasOwn |
| Freeze (shallow) | Object.freeze |
| Dynamic key | { [key]: value } |
| Safe nested access | ?. optional chaining |
| Same-value check | Object.is |
| Create with prototype | Object.create |
| Unique values | Set |
| Any-type keys | Map |
Practice Problems
Quick index
1. Fundamentals (T-001 – T-020)
2. Product dataset (T-021 – T-040)
3. Map, Set & advanced (T-041 – T-048)
1. Fundamentals (T-001 – T-020)
T-001: Create a user object using an object literal
Answer:
const user = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
}T-002: Create an object using new Object()
Answer:
const user = new Object()
user.id = 1
user.name = 'Alice'
user.email = 'alice@example.com'T-003: Access property with dot and bracket notation
Answer:
const user = { name: 'Alice', role: 'admin' }
user.name // 'Alice'
user['role'] // 'admin'
const key = 'name'
user[key] // 'Alice'T-004: Add and update properties
Answer:
const product = { id: 1, name: 'Mug' }
product.price = 12.99 // add
product.name = 'Ceramic Mug' // updateT-005: Delete a property with delete
Answer:
const user = { id: 1, name: 'Alice', temp: true }
delete user.temp
console.log(user) // { id: 1, name: 'Alice' }T-006: Check property with in vs Object.hasOwn
Answer:
const user = { name: 'Alice' }
'name' in user // true
'toString' in user // true — inherited!
Object.hasOwn(user, 'name') // true
Object.hasOwn(user, 'toString') // falseT-007: Destructure name and email
Answer:
const user = { id: 1, name: 'Alice', email: 'alice@example.com' }
const { name, email } = userT-008: Destructure with rename and default
Answer:
const user = { name: 'Alice' }
const { name: userName, role = 'viewer' } = user
// userName = 'Alice', role = 'viewer'T-009: Rest destructuring to omit password
Answer:
const user = { id: 1, name: 'Alice', password: 'secret', role: 'admin' }
const { password, ...safeUser } = user
// safeUser = { id: 1, name: 'Alice', role: 'admin' }T-010: Nested destructuring
Answer:
const data = {
user: { address: { city: 'NYC', zip: '10001' } },
}
const {
user: {
address: { city },
},
} = data
console.log(city) // 'NYC'T-011: Shallow clone with spread
Answer:
const original = { a: 1, nested: { b: 2 } }
const clone = { ...original }
clone.a = 99
original.a // 1 — top-level independentT-012: Deep clone with structuredClone
Answer:
const original = { a: 1, nested: { b: 2 } }
const deep = structuredClone(original)
deep.nested.b = 99
original.nested.b // 2 — unchangedT-013: Merge configs with spread
Answer:
const defaults = { theme: 'light', pageSize: 10 }
const overrides = { pageSize: 25, lang: 'en' }
const config = { ...defaults, ...overrides }
// { theme: 'light', pageSize: 25, lang: 'en' }T-014: Object.assign vs spread
Answer:
const defaults = { a: 1, b: 2 }
const extra = { b: 3, c: 4 }
const target = {}
Object.assign(target, defaults, extra) // mutates target
const merged = { ...defaults, ...extra } // new objectT-015: Object.keys, values, entries
Answer:
const scores = { alice: 90, bob: 75 }
Object.keys(scores) // ['alice', 'bob']
Object.values(scores) // [90, 75]
Object.entries(scores) // [['alice', 90], ['bob', 75]]T-016: Object.fromEntries
Answer:
const pairs = [
['x', 1],
['y', 2],
['z', 3],
]
const obj = Object.fromEntries(pairs)
// { x: 1, y: 2, z: 3 }T-017: Loop with Object.entries and for...of
Answer:
const user = { name: 'Alice', role: 'admin' }
for (const [key, value] of Object.entries(user)) {
console.log(key, value)
}T-018: freeze vs seal vs preventExtensions
Answer:
const obj = { a: 1 }
Object.preventExtensions(obj) // can't add keys
Object.seal(obj) // can't add/delete
Object.freeze(obj) // can't add/delete/change valuesT-019: Read-only property with defineProperty
Answer:
const user = {}
Object.defineProperty(user, 'id', {
value: 1,
writable: false,
enumerable: true,
configurable: false,
})T-020: Optional chaining and nullish coalescing
Answer:
const user = { profile: { name: 'Alice' } }
user.profile?.name // 'Alice'
user.address?.city // undefined
user.address?.city ?? 'Remote' // 'Remote'2. Product dataset (T-021 – T-040)
Shared setup:
const products = [
{ id: 'p1', name: 'Mug', categoryId: 'c1', price: 12.99, inStock: true },
{ id: 'p2', name: 'Book', categoryId: 'c2', price: 24.5, inStock: true },
{ id: 'p3', name: 'Pen', categoryId: 'c1', price: 2.99, inStock: false },
{ id: 'p4', name: 'Lamp', categoryId: 'c3', price: 45.0, inStock: true },
{ id: 'p5', name: 'Notebook', categoryId: 'c2', price: 8.5, inStock: true },
{ id: 'p6', name: 'Desk', categoryId: 'c3', price: 199.99, inStock: false },
]
const categories = { c1: 'Kitchen', c2: 'Stationery', c3: 'Furniture' }
const getCategoryName = (id) => categories[id] ?? 'Unknown'
const getCategoryIdByName = (name) => Object.entries(categories).find(([, n]) => n === name)?.[0]T-021: Filter products in "Stationery" category
Answer:
const stationeryId = getCategoryIdByName('Stationery') // 'c2'
const stationeryProducts = products.filter((p) => p.categoryId === stationeryId)
// Book, NotebookT-022: Format as "Mug (Kitchen)"
Answer:
const labeled = products.map((p) => `${p.name} (${getCategoryName(p.categoryId)})`)T-023: Find highest-priced product
Answer:
const highest = products.reduce((max, p) => (p.price > max.price ? p : max))
// Desk — 199.99T-024: Any product out of stock?
Answer:
const hasOutOfStock = products.some((p) => !p.inStock)
// trueT-025: Function filterByMinPrice
Answer:
function filterByMinPrice(list, minPrice) {
return list.filter((p) => p.price > minPrice)
}
filterByMinPrice(products, 20)
// Book, Lamp, DeskT-026: Extract product names only
Answer:
const names = products.map((p) => p.name)T-027: Total value of in-stock products
Answer:
const totalInStock = products.filter((p) => p.inStock).reduce((sum, p) => sum + p.price, 0)
// 91.99T-028: Any product priced below 5?
Answer:
const hasCheap = products.some((p) => p.price < 5)
// true — Pen (2.99)T-029: Find product with price exactly 8.5
Answer:
const notebook = products.find((p) => p.price === 8.5)T-030: Group products by categoryId
Answer:
const byCategory = Object.groupBy(products, (p) => p.categoryId)
// { c1: [Mug, Pen], c2: [Book, Notebook], c3: [Lamp, Desk] }T-031: Normalize products by id
Answer:
const productsById = Object.fromEntries(products.map((p) => [p.id, p]))
productsById['p4'].name // 'Lamp'T-032: Lookup product name and category by id
Answer:
function getProductDetails(id) {
const product = productsById[id]
if (!product) return null
return {
name: product.name,
category: getCategoryName(product.categoryId),
}
}T-033: Increase all prices by 10% immutably
Answer:
const withMarkup = products.map((p) => ({
...p,
price: +(p.price * 1.1).toFixed(2),
}))T-034: Count products per category name
Answer:
const countByCategory = products.reduce((acc, p) => {
const name = getCategoryName(p.categoryId)
acc[name] = (acc[name] ?? 0) + 1
return acc
}, {})
// { Kitchen: 2, Stationery: 2, Furniture: 2 }T-035: Log name and category with for...of
Answer:
const labelMap = Object.fromEntries(
products.map((p) => [p.id, { name: p.name, category: getCategoryName(p.categoryId) }]),
)
for (const [id, info] of Object.entries(labelMap)) {
console.log(id, info.name, info.category)
}T-036: Implement pick
Answer:
function pick(obj, keys) {
return Object.fromEntries(keys.filter((k) => k in obj).map((k) => [k, obj[k]]))
}
products.map((p) => pick(p, ['id', 'name', 'price']))T-037: Implement omit
Answer:
function omit(obj, keys) {
const exclude = new Set(keys)
return Object.fromEntries(Object.entries(obj).filter(([k]) => !exclude.has(k)))
}
products.map((p) => omit(p, ['inStock']))T-038: Deep merge defaults and overrides
Answer:
function deepMerge(target, source) {
const out = { ...target }
for (const [key, value] of Object.entries(source)) {
if (value && typeof value === 'object' && !Array.isArray(value) && target[key] && typeof target[key] === 'object') {
out[key] = deepMerge(target[key], value)
} else {
out[key] = value
}
}
return out
}
const defaults = { theme: 'light', settings: { fontSize: 14, bold: false } }
const overrides = { settings: { fontSize: 16 } }
deepMerge(defaults, overrides)
// { theme: 'light', settings: { fontSize: 16, bold: false } }T-039: Implement shallowEqual
Answer:
function shallowEqual(a, b) {
if (Object.is(a, b)) return true
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length) return false
return keysA.every((key) => Object.is(a[key], b[key]))
}T-040: Convert categories object to Map
Answer:
const categoryMap = new Map(Object.entries(categories))
categoryMap.get('c2') // 'Stationery'3. Map, Set & advanced (T-041 – T-048)
T-041: Create and iterate a Map
Answer:
const map = new Map()
map.set('name', 'Alice')
map.set(42, 'answer')
map.set({ id: 1 }, 'meta')
for (const [key, value] of map) {
console.log(key, value)
}T-042: Object to Map via Object.entries
Answer:
const obj = { a: 1, b: 2 }
const map = new Map(Object.entries(obj))T-043: Map back to plain object
Answer:
const map = new Map([
['a', 1],
['b', 2],
])
const obj = Object.fromEntries(map)
// { a: 1, b: 2 }T-044: Deduplicate tags with Set
Answer:
const tags = ['js', 'web', 'js', 'api', 'web']
const unique = [...new Set(tags)]
// ['js', 'web', 'api']T-045: Count word occurrences
Answer:
const sentence = 'the cat and the dog'
const counts = {}
for (const word of sentence.split(' ')) {
counts[word] = (counts[word] ?? 0) + 1
}
// { the: 2, cat: 1, and: 1, dog: 1 }T-046: Object.create with inherited greet
Answer:
const personProto = {
greet() {
return `Hi, I'm ${this.name}`
},
}
const user = Object.create(personProto)
user.name = 'Alice'
user.greet() // "Hi, I'm Alice"T-047: When to use Map instead of object
Answer:
// DOM node → metadata (object keys get coerced to strings)
const nodeData = new Map()
const el = document.createElement('div')
nodeData.set(el, { clicks: 0 }) // object as key worksT-048: Safe JSON parse with fallback
Answer:
function safeParse(json, fallback = null) {
try {
return JSON.parse(json)
} catch {
return fallback
}
}
safeParse('{"a":1}') // { a: 1 }
safeParse('not json', {}) // {}Quick reference — method by task
| Tasks | Primary APIs |
|---|---|
| T-001 – T-006 | literals, dot/bracket, delete, in, Object.hasOwn |
| T-007 – T-010 | destructuring, rest, nested destructuring |
| T-011 – T-014 | spread, structuredClone, Object.assign |
| T-015 – T-020 | Object.keys/values/entries/fromEntries, freeze/seal, defineProperty, ?. |
| T-021 – T-040 | filter, map, reduce, Object.groupBy, normalize, pick/omit |
| T-041 – T-048 | Map, Set, Object.create, safe JSON parse |
Interview tip: Practice explaining T-006 (in vs hasOwn), T-031 (normalization), and T-047 (Map vs Object) aloud.
Subscribe to my newsletter
Stay up to date and get notified when I share new contents.
No spam ever, unsubscribe anytime