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 questions0 beginner5 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 →
Q2Core

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

Rendering StrategiesCore JSType SystemReact FundamentalsFunctionsMicrofrontendsGenericsAsync JSHooksMonorepoArrays'this' KeywordUtility TypesError HandlingModern JSBundle OptimizationPerformanceDOM & EventsState ManagementClasses & OOPCaching StrategiesComponent PatternsAdvanced TypesAuthenticationReact RouterFormsAdvanced PatternsFrontend SecurityConcurrent ReactServer ComponentsTestingEcosystemNetwork OptimizationCore Web VitalsBrowser APIs

Ready to practice Objects?

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

Start Free Practice →