Intermediate1 questionFull Guide

JavaScript Object Interview Questions

Objects are the building blocks of JavaScript. Master property descriptors, copying, iteration, and prototype-based patterns.

The Mental Model

Picture a filing cabinet. Each drawer has a label (the key) and contains documents (the value). You can add new drawers, remove old ones, relabel them, or swap the contents. The cabinet itself — that collection of labeled drawers — is an object. But JavaScript objects are more than passive storage. They're living structures. Properties have hidden attributes that control whether they can be changed, enumerated, or deleted. Objects can delegate missing properties to other objects through prototypes. They can be sealed, frozen, or monitored with Proxies. And every function, array, class, and regular expression in JavaScript is also an object — they just have extra capabilities bolted on. The key insight: in JavaScript, almost everything is an object or behaves like one. Strings, numbers, and booleans temporarily borrow object behavior when you access properties on them. Functions are callable objects. Arrays are indexed objects. Mastering objects means understanding the engine room of JavaScript itself.

The Explanation

Creating objects — four ways

// 1. Object literal — most common
const user = {
  name: 'Alice',
  age: 30,
  greet() { return `Hi, I'm ${this.name}` }
}

// 2. Object.create(proto) — set prototype explicitly
const base = { greet() { return `Hi, I'm ${this.name}` } }
const user = Object.create(base)
user.name = 'Alice'

// 3. Constructor function / class
class User {
  constructor(name, age) { this.name = name; this.age = age }
}
const user = new User('Alice', 30)

// 4. Object.fromEntries — build from key-value pairs
const fields = [['name', 'Alice'], ['age', 30]]
const user = Object.fromEntries(fields)  // { name: 'Alice', age: 30 }

Property shorthand, computed keys, getters, setters

const name = 'Alice'
const age  = 30

// Shorthand — variable name becomes the key
const user = { name, age }  // { name: 'Alice', age: 30 }

// Computed keys — any expression inside []
const field = 'email'
const user  = {
  [field]: 'alice@example.com',
  [`${field}Valid`]: true
}

// Getters and setters — computed properties with logic
const circle = {
  _r: 5,
  get radius()  { return this._r },
  set radius(r) {
    if (r < 0) throw new RangeError('Positive only')
    this._r = r
  },
  get area() { return Math.PI * this._r ** 2 }
}
circle.radius = 10
circle.area   // 314.159...

Property descriptors — the hidden layer

Every property has a descriptor with four attributes. Most developers never see these, but they control everything about how a property behaves.

const obj = { name: 'Alice' }

Object.getOwnPropertyDescriptor(obj, 'name')
// { value: 'Alice', writable: true, enumerable: true, configurable: true }
//   writable:     can the value be changed?
//   enumerable:   shows in for..in / Object.keys / JSON.stringify?
//   configurable: can descriptor be changed or property deleted?

// Define a hidden, read-only property
Object.defineProperty(obj, 'id', {
  value: 42,
  writable: false,    // read-only
  enumerable: false,  // hidden from Object.keys and JSON.stringify
  configurable: false // permanent
})

obj.id = 99          // silently fails (strict mode: TypeError)
obj.id               // 42 — unchanged
Object.keys(obj)     // ['name'] — id is non-enumerable
JSON.stringify(obj)  // '{"name":"Alice"}' — id excluded

Object immutability — three levels

const config = { host: 'localhost', port: 3000, db: { timeout: 5000 } }

// preventExtensions — no new properties
Object.preventExtensions(config)
config.host = 'prod'    // ✓ modify existing
config.newProp = 'x'    // ✗ can't add new

// seal — no add, no delete; existing writable props still mutable
Object.seal(config)
config.host = 'prod'    // ✓ still writable
delete config.host      // ✗ can't delete

// freeze — completely locked (SHALLOW)
Object.freeze(config)
config.host = 'changed' // ✗ nothing changes
config.db.timeout = 999 // ✓ nested object NOT frozen — still mutable!

// Deep freeze
function deepFreeze(obj) {
  Object.getOwnPropertyNames(obj).forEach(key => {
    const val = obj[key]
    if (val && typeof val === 'object') deepFreeze(val)
  })
  return Object.freeze(obj)
}

Copying and merging

const user = { name: 'Alice', address: { city: 'Mumbai' } }

// Shallow copy — two ways, same result
const copy1 = Object.assign({}, user)
const copy2 = { ...user }

// Shallow means nested objects are SHARED
copy2.address.city = 'Delhi'
user.address.city   // 'Delhi' — same reference!

// Deep clone options
const deep1 = JSON.parse(JSON.stringify(user))
// Limitations: loses functions, undefined, Date becomes string, no circular refs

const deep2 = structuredClone(user)
// Modern (2022+): handles Date, Map, Set, ArrayBuffer, circular refs
// Does NOT handle functions or class instances

// Merging — later keys win
const defaults = { theme: 'light', lang: 'en', timeout: 5000 }
const prefs    = { theme: 'dark' }
const config   = { ...defaults, ...prefs }
// { theme: 'dark', lang: 'en', timeout: 5000 }

Object utility methods — the full toolkit

const user = { name: 'Alice', age: 30, role: 'admin' }

Object.keys(user)    // ['name', 'age', 'role']  — own enumerable keys
Object.values(user)  // ['Alice', 30, 'admin']
Object.entries(user) // [['name','Alice'], ['age',30], ['role','admin']]

// fromEntries — inverse of entries; transform and rebuild
const sanitized = Object.fromEntries(
  Object.entries(user)
    .filter(([k]) => k !== 'role')         // remove role
    .map(([k, v]) => [k, String(v).trim()]) // trim all values
)
// { name: 'Alice', age: '30' }

// Checking properties
'name'  in user                // true  — own + prototype chain
Object.hasOwn(user, 'name')    // true  — own only (modern preferred API)
Object.hasOwn(user, 'toString') // false — inherited, not own

// Property exists but value is undefined:
const obj = { count: undefined }
obj.count === undefined         // true — but property EXISTS
Object.hasOwn(obj, 'count')     // true — confirms it exists
Object.hasOwn(obj, 'missing')   // false — this one doesn't exist

Optional chaining and nullish coalescing

const user = { profile: { address: { city: 'Mumbai' } } }

// Without optional chaining — crashes if anything is null/undefined
const city = user.profile.address.city  // fine here, but fragile

// Optional chaining — short-circuits at first null/undefined
const city    = user?.profile?.address?.city     // 'Mumbai'
const country = user?.profile?.address?.country  // undefined (no error)
const phone   = user?.contact?.phone             // undefined (contact missing)

// Methods and bracket notation
user?.getPermissions?.()  // calls only if method exists
user?.roles?.[0]          // first role only if roles is defined

// Nullish coalescing — default only for null/undefined (not 0, '', false)
const city    = user?.profile?.address?.city ?? 'Unknown'
const timeout = config?.timeout ?? 5000
const count   = data?.count ?? 0  // preserves 0 if explicitly set to 0
// vs || which replaces ALL falsy values — almost always wrong for defaults

Iteration order

// Property order rules (ES2015+):
// 1. Integer indices (array-like keys) — ascending numeric order
// 2. String keys — insertion order
// 3. Symbol keys — insertion order

const obj = {
  b: 2,
  a: 1,
  1: 'one',
  0: 'zero',
  c: 3
}

Object.keys(obj)  // ['0', '1', 'b', 'a', 'c']
// Integers first (sorted), then strings in insertion order

// for...in respects the same order
// JSON.stringify respects the same order

Common Misconceptions

⚠️

Many devs think spread or Object.assign creates a fully independent copy — but actually both create a shallow copy. Top-level properties are new, but nested objects are still shared references. Modifying a nested property on the copy modifies the original. Use structuredClone() for a true deep copy of plain data structures.

⚠️

Many devs think Object.freeze() makes an object deeply immutable — but actually freeze is shallow. It locks the object's own direct properties, but any nested objects remain fully mutable. freezing { config: { port: 3000 } } prevents reassigning config.port but not config.port = 3000 — wait, wrong: you CAN still do config.port.subprop if port were an object. The frozen object's own property pointer is locked, but what it points to is not.

⚠️

Many devs think for...in is safe for iterating object properties — but actually for...in walks the entire prototype chain and includes inherited enumerable properties. Libraries that extend Object.prototype (like some older polyfills) can inject unexpected keys. Use Object.keys() for own-only iteration, or guard with Object.hasOwn() inside for...in.

⚠️

Many devs think checking property === undefined tells you whether it exists — but actually a property can deliberately be set to undefined and still exist. The value undefined and property absence look identical with this check. Object.hasOwn() is the reliable way to distinguish between "property exists with value undefined" and "property does not exist at all."

⚠️

Many devs think Object.keys, Object.values, and Object.entries cover all properties — but actually all three exclude inherited properties, non-enumerable properties, and Symbol-keyed properties. If you need non-enumerable own properties, use Object.getOwnPropertyNames(). If you need Symbol keys, use Object.getOwnPropertySymbols().

⚠️

Many devs think property descriptor features are niche or framework-only concerns — but actually getters/setters power React controlled inputs, Vue reactivity, MobX observables, and every JavaScript ORM. Understanding descriptors is what lets you write a class property that computes from other properties, validates on write, or fires side effects on change.

Where You'll See This in Real Code

Vue 2's entire reactivity system was Object.defineProperty — Vue replaced each property on your data object with a getter/setter pair at initialization. The getter tracked dependencies; the setter triggered re-renders. This is why Vue 2 couldn't detect property additions (defineProperty only works on existing properties). Vue 3 switched to Proxy, which intercepts all operations including additions, giving it reactivity superpowers Vue 2 never had.

Redux enforces immutability with Object.freeze in development mode — Redux Toolkit automatically deep-freezes state in development so you get a clear TypeError if you accidentally mutate rather than returning a new object. This converts the "rule" of immutability into a hard runtime error, making illegal state mutations impossible to miss during development.

TypeScript's Readonly<T> and as const compile to nothing at runtime but map directly to the mental model of Object.freeze at the type level — as const on an object literal marks every property as readonly and literal-typed, which is the static analysis equivalent of deep-freezing. Understanding freeze makes understanding as const intuitive.

Lodash's merge function (deep merge) exists because Object.assign's shallow merge is insufficient for nested configuration objects. When two config objects both have a nested database key with different sub-properties, Object.assign replaces the whole database object rather than merging its properties. Deep merge walks nested objects and merges at every level.

Next.js API routes use Object.fromEntries(request.headers) to convert header iterables to plain objects for logging and middleware — Headers implements the iterator protocol but isn't a plain object, so fromEntries creates a snapshot. This pattern (iterable → plain object) is a common bridge between Web API types and plain JavaScript objects.

The structuredClone() API (added natively in 2022) was specifically added to browsers and Node.js because the JSON.parse(JSON.stringify()) deep clone hack fails on Dates, Maps, Sets, undefined, and circular references. Every major JavaScript runtime now supports structuredClone, making it the correct default for deep cloning anything more complex than plain JSON data.

Interview Cheat Sheet

  • Object literal: { key: value } — shorthand, computed keys, getters/setters all supported
  • Property descriptor: { value, writable, enumerable, configurable } — controls all behavior
  • Object.freeze(): shallow — own direct properties locked; nested objects still mutable
  • Spread/Object.assign: shallow copy — nested objects are shared references
  • structuredClone(): deep clone — handles Date, Map, Set, circular refs; not functions
  • Object.keys/values/entries: own enumerable only — no inherited, non-enumerable, or Symbols
  • 'key' in obj: own + prototype; Object.hasOwn(obj, 'key'): own only (modern preferred)
  • Optional chaining (?.) short-circuits on null/undefined — no error
  • Nullish coalescing (??): default for null/undefined only — 0 and '' are preserved
  • Property order: integer keys ascending → string keys insertion order → Symbol keys insertion order
💡

How to Answer in an Interview

  • 1.Shallow copy vs deep copy is the single most asked object question — name both methods and immediately explain shallow's limitation
  • 2.Object.freeze being shallow trips up most devs — demonstrate with a nested mutation
  • 3.Descriptor knowledge (enumerable: false explaining why some props don't show in JSON.stringify) signals senior-level understanding
  • 4.Vue 2 defineProperty vs Vue 3 Proxy is the best real-world application of descriptor knowledge
  • 5.structuredClone over JSON round-trip shows you know modern APIs, not just workarounds
📖 Deep Dive Articles
null vs undefined in JavaScript: What They Mean, When They Appear, and How to Handle Each7 min readTop 50 JavaScript Interview Questions (With Deep Answers)18 min readJavaScript Prototypes Explained: The Object Model Behind Every Class and Method10 min read

Practice Questions

1 question
#01

Method extracted from object

🟡 Medium'this' Binding

Related Topics

JavaScript Array Interview Questions
Intermediate·8–12 Qs
JavaScript Class Interview Questions
Intermediate·5–8 Qs
JavaScript Prototype & Prototypal Inheritance Interview Questions
Advanced·6–10 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz