JavaScript · Objects

Objects Interview Questions
With Answers & Code Examples

9 carefully curated Objects interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
9 questions1 beginner4 core4 advanced
Q1Core

How does prototypal inheritance work in JavaScript?

💡 Hint: Every object has a [[Prototype]] link — property lookup walks the chain

Every JS object has an internal [[Prototype]] link. Property lookup walks up the chain until found or null.

const animal = { speak() { return '...'; } };
const dog = Object.create(animal); // dog's proto = animal
dog.name = 'Rex';
dog.speak(); // Found on chain → '...'

// ES6 class is sugar over this
class Dog extends Animal {
  speak() { return 'Woof!'; }
}
💡 Use hasOwnProperty() to check if a property is directly on the object vs inherited.
Practice this question →
Q2Beginner

What is the difference between shallow copy and deep copy?

💡 Hint: Shallow copies references; deep copies recursively

Shallow copy: top-level properties only — nested objects are still shared references. Deep copy: recursively copies everything.

const orig = { a: 1, nested: { b: 2 } };

// Shallow — spread, Object.assign
const shallow = { ...orig };
shallow.nested.b = 99;
console.log(orig.nested.b); // 99 — orig mutated!

// Deep copy
const deep = structuredClone(orig); // ✅ modern recommended
// JSON.parse(JSON.stringify()) — simple but lossy (no undefined/Dates)
Practice this question →
Q3Advanced

What are property descriptors and property flags (writable, enumerable, configurable)?

💡 Hint: Every property has a descriptor controlling whether it can be changed, seen, or deleted

Every object property has a descriptor with 3 flags:

  • writable — can the value be changed?
  • enumerable — does it show up in for...in / Object.keys()?
  • configurable — can the descriptor be changed? Can the property be deleted?
const obj = {};

Object.defineProperty(obj, 'ID', {
  value: 42,
  writable: false,    // read-only
  enumerable: false,  // hidden from loops
  configurable: false // can't redefine or delete
});

obj.ID = 99;              // silently fails (TypeError in strict)
console.log(obj.ID);      // 42

Object.keys(obj);         // [] — ID is non-enumerable
delete obj.ID;            // false — non-configurable

// View a property's descriptor
Object.getOwnPropertyDescriptor(obj, 'ID');
// { value: 42, writable: false, enumerable: false, configurable: false }

// Regular property defaults:
// { value: ..., writable: true, enumerable: true, configurable: true }
💡 Object.freeze() sets writable + configurable to false for all props. Object.seal() sets configurable to false but keeps writable. Both use property descriptors under the hood.
Practice this question →
Q4Core

What are getters and setters in JavaScript?

💡 Hint: Accessor properties — run a function on read (get) or write (set)

Getters and setters are special methods that execute code when a property is read or written — they look like properties but behave like functions.

const user = {
  firstName: 'John',
  lastName: 'Doe',

  get fullName() {
    return `${this.firstName} ${this.lastName}`; // computed on read
  },

  set fullName(val) {
    [this.firstName, this.lastName] = val.split(' '); // validated on write
  }
};

console.log(user.fullName); // 'John Doe' — runs getter
user.fullName = 'Jane Smith'; // runs setter
console.log(user.firstName); // 'Jane'

// In a class
class Temperature {
  constructor(celsius) { this._c = celsius; }

  get fahrenheit() { return this._c * 9/5 + 32; }
  set fahrenheit(f) { this._c = (f - 32) * 5/9; }
}

const t = new Temperature(0);
console.log(t.fahrenheit); // 32
t.fahrenheit = 212;
console.log(t._c);         // 100
💡 Use getters for derived/computed values. Use setters for validation. Avoid getter/setter pairs that call each other — infinite loops!
Practice this question →
Q5Core

What is the difference between Object.freeze() and Object.seal()?

💡 Hint: freeze = truly immutable; seal = no add/delete but values can change

Both prevent structural changes but differ in degree:

  • Object.freeze() — no add, no delete, no value change. Completely locked.
  • Object.seal() — no add, no delete, but existing values CAN be changed.
// freeze
const config = Object.freeze({ host: 'localhost', port: 3000 });
config.port = 9999;     // silently fails (TypeError in strict)
config.debug = true;    // silently fails
delete config.host;     // false
console.log(config.port); // 3000 — unchanged

// seal
const sealed = Object.seal({ x: 1, y: 2 });
sealed.x = 99;          // ✅ allowed — value change is OK
sealed.z = 3;           // ❌ silently fails — no new props
delete sealed.x;        // ❌ fails — no delete

// Critical gotcha: BOTH are SHALLOW
const frozen = Object.freeze({ nested: { a: 1 } });
frozen.nested.a = 99;   // ⚠️ WORKS — nested object is not frozen!

// Deep freeze (recursive)
function deepFreeze(obj) {
  Object.getOwnPropertyNames(obj).forEach(name => {
    if (typeof obj[name] === 'object' && obj[name] !== null) {
      deepFreeze(obj[name]);
    }
  });
  return Object.freeze(obj);
}
💡 Use freeze() for config constants and action type objects in Redux. Remember it's shallow — deep freeze recursively if needed.
Practice this question →
Q6Core

What are the different ways to enumerate object properties?

💡 Hint: for...in, Object.keys, Object.values, Object.entries — differ in own vs inherited, enumerable vs all

Each enumeration method has different behavior around own properties, inherited properties, and enumerability:

const parent = { inherited: true };
const obj = Object.create(parent); // obj's prototype is parent
obj.a = 1;
obj.b = 2;
Object.defineProperty(obj, 'hidden', { value: 3, enumerable: false });

// for...in — own + inherited, enumerable only
for (const k in obj) console.log(k); // 'a', 'b', 'inherited'

// Object.keys — own properties, enumerable only ← most common
Object.keys(obj);    // ['a', 'b']

// Object.values — own, enumerable, values
Object.values(obj);  // [1, 2]

// Object.entries — own, enumerable, [key, value] pairs
Object.entries(obj); // [['a', 1], ['b', 2]]

// Object.getOwnPropertyNames — own, ALL (including non-enumerable)
Object.getOwnPropertyNames(obj); // ['a', 'b', 'hidden']

// Check if own property
obj.hasOwnProperty('a');         // true
obj.hasOwnProperty('inherited'); // false
Object.hasOwn(obj, 'a');         // ES2022 — preferred over hasOwnProperty
💡 Use Object.keys/values/entries in modern code — they only return own enumerable properties. Use for...in only if you explicitly need inherited properties (rare).
Practice this question →
Q7Advanced

How does instanceof work and what are its limitations?

💡 Hint: Walks the prototype chain looking for constructor.prototype — can be fooled

instanceof checks if a constructor's prototype appears anywhere in an object's prototype chain.

class Animal {}
class Dog extends Animal {}

const dog = new Dog();
dog instanceof Dog;    // true — Dog.prototype in chain
dog instanceof Animal; // true — Animal.prototype in chain too
dog instanceof Object; // true — everything inherits from Object

// How it works internally:
// dog.__proto__ === Dog.prototype ✓ → true

// Limitation 1: Can be fooled by setPrototypeOf
const fake = Object.create(Dog.prototype);
fake instanceof Dog; // true — but Dog() was never called!

// Limitation 2: Cross-realm failure
// Arrays from iframes: arr instanceof Array → false!
// Use Array.isArray() — realm-safe

// Limitation 3: Primitives always fail
'hello' instanceof String;     // false (primitive, not object)
new String('hello') instanceof String; // true (wrapped object)

// Better type checking alternatives:
Array.isArray([]);                          // ✅ realm-safe
typeof 'hello';                             // 'string'
Object.prototype.toString.call([]);         // '[object Array]'
💡 instanceof tests the prototype chain, not the constructor. For safe type checks use Array.isArray(), typeof, or Object.prototype.toString.call().
Practice this question →
Q8Advanced

What are mixins in JavaScript and why are they used?

💡 Hint: Copy methods from multiple sources into a class — avoids single-inheritance limits

A mixin is a pattern to copy methods from one object into another class or prototype, enabling code reuse without inheritance chains.

// Mixin objects (plain objects with methods)
const Serializable = {
  serialize() { return JSON.stringify(this); },
};

const Validatable = {
  validate() {
    return Object.values(this).every(v => v !== null && v !== undefined);
  }
};

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

// Apply mixins — copy methods onto the class prototype
Object.assign(User.prototype, Serializable, Validatable);

const u = new User('Alice', 'a@b.com');
u.serialize(); // '{"name":"Alice","email":"a@b.com"}'
u.validate();  // true

// More modern approach: mixin factory functions
const withLogging = (Base) => class extends Base {
  log(msg) { console.log(`[${this.constructor.name}] ${msg}`); }
};

class LoggableUser extends withLogging(User) {}

Mixins solve the diamond problem of multiple inheritance — compose behavior from multiple independent sources.

💡 React class component mixins were deprecated in favour of Hooks. Hooks are the modern mixin equivalent — reusable stateful logic without inheritance.
Practice this question →
Q9Advanced

What are native prototypes and how can you safely extend them?

💡 Hint: Array.prototype, String.prototype etc — extending them affects ALL instances; almost always a bad idea

All built-in types (Array, String, Object, Function…) have prototypes with their methods. Every array shares Array.prototype.

// How it works — built-in prototype chain
const arr = [1, 2, 3];
// arr.__proto__ === Array.prototype ✓
// Array.prototype.__proto__ === Object.prototype ✓

// All arrays share Array.prototype methods
arr.map === Array.prototype.map;   // true — same reference

// Checking native prototype
Array.prototype.includes;   // function — built-in
String.prototype.padStart;  // function — built-in

// ❌ Bad: extending native prototypes (prototype pollution risk)
Array.prototype.last = function() { return this[this.length - 1]; };
// Now EVERY array in ALL your code + libraries has .last — collisions!

// ❌ Famous historical mistake: Prototype.js library
// It extended Array.prototype and broke all for...in loops on arrays

// ✅ If you must extend (only in polyfills — check first)
if (!Array.prototype.myMethod) { // always guard with existence check
  Array.prototype.myMethod = function() { ... };
}

// ✅ Better: use utility functions or subclassing
class SuperArray extends Array {
  last() { return this[this.length - 1]; }
}
const sa = new SuperArray(1, 2, 3);
sa.last(); // 3 — only SuperArray instances affected
💡 Rule: never extend native prototypes in application code or libraries. Exception: polyfills (always check for existence first). It's the JS equivalent of monkey-patching — dangerous at scale.
Practice this question →

Other JavaScript Interview Topics

Core JSFunctionsAsync JSArrays'this' KeywordError HandlingModern JSPerformanceDOM & EventsBrowser APIs

Ready to practice Objects?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →