close

DEV Community

Keyur Gohil
Keyur Gohil

Posted on

11 JavaScript Interview Questions Every Dev Should Know — Part 3: Objects, Prototypes & OOP

Part 3 of the series dives into JavaScript's object model — a topic that trips people up because JS's approach (prototypal inheritance) is fundamentally different from classical OOP languages like Java or C#.

Catch up on Part 1: Fundamentals and Part 2: Functions & Closures if you missed them.


Q1. How does prototypal inheritance work in JavaScript?

Every object in JavaScript carries an internal, hidden link to another object, known as its [[Prototype]] — accessible in practice through Object.getPrototypeOf() or the older, legacy __proto__ accessor. Rather than defining behavior through rigid class hierarchies, objects share and reuse behavior by pointing to other objects that already have that behavior defined.

When you try to access a property or method on an object, the engine first checks whether the object has that property directly ("own property"). If it doesn't, the engine doesn't just give up — it walks up to the object's prototype and checks there, then that prototype's prototype, and so on, continuing this chain until either the property is found or the chain terminates at null (the end of every prototype chain). This lookup process is called the prototype chain, and it's the actual mechanism underlying everything that looks like inheritance in JavaScript, including classes.

Q2. What is the difference between Object.create() and a constructor function?

Object.create(proto) is the most direct way to work with prototypal inheritance: it creates and returns a brand-new, empty object whose [[Prototype]] is set to whatever object you passed in as an argument. There's no constructor function involved at all — you're explicitly wiring up the prototype relationship yourself, which makes the underlying mechanism very transparent, though it's used less often in everyday code than the class syntax.

A constructor function, called with the new keyword, is a more automated (and historically more common) way to achieve the same result, but it does several things behind the scenes at once: it creates a new object, links that object's prototype to the constructor function's .prototype property, runs the constructor's code with this bound to that new object (so you can attach instance properties), and then returns the object automatically — all without you having to manage the prototype linkage manually.

Q3. What is the difference between classical inheritance and prototypal inheritance?

Classical inheritance, found in languages like Java or C#, is built around the concept of classes — abstract blueprints, defined at compile time, that describe the shape and behavior objects of that type will have. Instances are created from these classes, and inheritance forms a fixed, hierarchical tree structure: a subclass extends a superclass, and that relationship is generally locked in at the time the code is written.

Prototypal inheritance takes a fundamentally different, more flexible approach: there are no true "classes" underneath — only objects, and those objects inherit directly from other objects through the prototype chain described earlier. Because prototypes are just regular objects, they can be modified dynamically at runtime (you can add a method to a prototype after objects have already been created from it, and every existing instance will immediately gain access to that new method through the chain), which is a level of flexibility classical class hierarchies typically don't allow.

Q4. What are ES6 classes, and are they truly a new object model?

ES6 introduced the class keyword, giving JavaScript a syntax that looks very familiar to developers coming from classical OOP languages — with constructor, extends, super, and method definitions that resemble Java or C#. This was a deliberate design choice to make JavaScript more approachable, but underneath, class is almost entirely syntactic sugar layered on top of the exact same prototype-based system that's always existed in the language.

When you define a method inside a class body, it's actually being attached to the constructor function's .prototype object, exactly as it would be if you'd manually assigned it with SomeConstructor.prototype.methodName = function() {} in pre-ES6 code. What class does add beyond pure syntax are a few genuine behavioral differences: class bodies always execute in strict mode, class declarations are hoisted but land in the Temporal Dead Zone (unlike function declarations, which are fully usable before their definition), and attempting to call a class without new throws an error rather than silently misbehaving.

Q5. What is the difference between an instance property and a prototype method?

An instance property is a property that lives directly on an individual object, typically set up inside a constructor (or class constructor) using this.propertyName = value. Because it's assigned per-instance, every single object created from that constructor gets its own independent copy of the property — mutating one instance's property has zero effect on any other instance.

A prototype method, by contrast, is defined once on the shared .prototype object of the constructor, and every instance accesses that same single copy through the prototype chain rather than each having its own duplicate. This distinction matters a lot for memory efficiency: if you have a constructor for creating a million objects and each object needs a method, defining that method on the prototype means only one copy of that function exists in memory total, whereas defining it as an instance property (e.g., this.method = function() {} inside the constructor) would create a million separate function instances, one per object.

Q6. What is the purpose of the new keyword?

The new keyword transforms an ordinary function call into a constructor call, and it silently performs four distinct steps on your behalf every time it's used. First, it creates a brand-new, empty plain object. Second, it sets that new object's internal [[Prototype]] link to point at the constructor function's .prototype property, wiring up the inheritance chain. Third, it executes the constructor function's code, with this inside that function bound to the newly created object, allowing the constructor body to attach properties via this.prop = value. Fourth and finally, unless the constructor function explicitly returns some other object of its own, new automatically returns the newly created and now-populated object as the result of the whole expression.

Forgetting to use new when calling a function meant to be a constructor is a classic source of bugs in older JavaScript code — without it, this inside the function falls back to the global object (or undefined in strict mode), silently corrupting global state or throwing errors instead of properly constructing a new instance. Modern class syntax protects against this by throwing a TypeError if you try to call a class without new.

Q7. What is a getter and setter in JavaScript objects?

Getters and setters are special object methods, defined using the get and set keywords, that let you attach custom logic to what looks like a plain property from the outside, but actually runs a function whenever that property is read (get) or assigned to (set). The consumer of the object never has to know they're calling a function — they just write obj.value or obj.value = 10 as if it were an ordinary data property.

const obj = {
  _value: 0,
  get value() { return this._value; },
  set value(v) { this._value = v < 0 ? 0 : v; }
};
Enter fullscreen mode Exit fullscreen mode

This pattern is extremely useful for validation (rejecting or clamping invalid values before they're stored, as in the example above), computed properties (deriving a value on the fly from other internal state rather than storing it directly), and encapsulation more generally — giving you a seam to intercept reads and writes without changing the object's external API if your internal implementation needs to change later.

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

Object.freeze() is the strictest form of object locking available natively in JavaScript: once applied, you cannot add new properties, remove existing properties, or change the value of any existing property — any attempt to do so either fails silently (in non-strict mode) or throws a TypeError (in strict mode). This makes the object fully immutable at that level.

Object.seal() is a more moderate restriction: like freeze(), it prevents adding new properties and prevents deleting existing ones, but unlike freeze(), it still allows you to modify the value of any property that was already marked as writable before sealing. Both freeze() and seal() operate shallowly — they only lock the top-level object itself, and any nested objects inside it remain fully mutable unless you explicitly freeze or seal those inner objects too, recursively, yourself.

Q9. What is the difference between an object's own properties and inherited properties?

An "own" property is one that exists directly on the specific object itself, having been explicitly assigned to it — as opposed to a property the object only appears to have because it's accessible through the prototype chain from somewhere further up. From the outside, both kinds of properties often look identical when you access them normally (obj.someProp works the same either way), but the distinction becomes important when you need to know exactly what an object itself contains versus what it merely has access to.

Object.hasOwn(obj, prop) (the modern replacement for the older obj.hasOwnProperty(prop)) checks exclusively for own properties, returning false for anything inherited, even if that inherited property is perfectly accessible via normal dot notation. The in operator and for...in loops, on the other hand, check both own and inherited enumerable properties, which is a common source of bugs when iterating over an object with for...in without an additional hasOwn check to filter out unwanted inherited properties.

Q10. What is composition over inheritance, and why is it often preferred in JavaScript?

Composition is a design approach that builds up complex behavior by combining several small, focused, independent pieces of functionality — functions, objects, or mixins — rather than by building a deep hierarchy of classes that each extend the one before it. Instead of asking "what is this thing a type of?" (the inheritance question), composition asks "what capabilities does this thing need, and which existing pieces can I assemble to provide them?"

Deep inheritance chains have well-known downsides in real-world codebases: they tend to be fragile (a change to a base class can have unpredictable ripple effects on every descendant), they force awkward hierarchies when behavior doesn't cleanly fit a single "is-a" relationship, and they tightly couple unrelated pieces of functionality together just because they happen to share an ancestor. Composition avoids these problems by keeping pieces of functionality small, independently testable, and freely combinable in whatever configuration a given object actually needs — a philosophy that's especially prominent in modern React (hooks and composed components) and general JavaScript idioms.

Q11. What is a mixin in JavaScript?

A mixin is a pattern for sharing a set of reusable methods or behaviors across multiple otherwise-unrelated objects or classes, without requiring them to be linked through a single-inheritance chain. Since JavaScript (like most languages with prototypal or class-based inheritance) only allows an object or class to have one direct prototype/parent, mixins provide a workaround for situations where you want to share functionality from multiple independent sources at once.

The most common implementation copies methods directly from a plain "mixin object" onto a target's prototype, effectively merging the extra behavior in:

class Duck {}
const canFly = { fly() { console.log(`${this.name} is flying`); } };
const canSwim = { swim() { console.log(`${this.name} is swimming`); } };
Object.assign(Duck.prototype, canFly, canSwim);
Enter fullscreen mode Exit fullscreen mode

This lets a single class "borrow" capabilities from as many mixin sources as needed, sidestepping the single-inheritance limitation while keeping each piece of shared behavior small, focused, and independently reusable across completely unrelated class hierarchies.


Next in the series: Asynchronous JavaScript — the event loop, Promises, async/await, and the questions that separate junior from senior candidates.

Enjoying the series? Follow along and share which of these questions you'd actually get asked in an interview!

Top comments (0)