JavaScript objects and prototypes interview questions, with answers
Objects are where JavaScript differs most from class-based languages, and interviewers probe the difference. Variables hold references, so copying and comparing objects behave differently from numbers and strings. Inheritance works through a chain of prototype links rather than classes, and the class keyword is a tidier way to build that chain, not a different model. The questions below cover references and copies, the prototype chain, classes, and the property controls that trip people up. Every output shown was produced by running the code in Node.js.
The questions start with how objects are copied and compared, then the prototype chain and classes, then freezing and accessors. Then take the free JavaScript diagnostic — ten questions across every JavaScript topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.How are objects copied and compared in JavaScript?
In short: By reference: assigning an object copies the reference, not the object, and == or === is true only when both sides are the same object, never for two objects with equal contents.
An object variable holds a reference, so const b = a makes two names for one object, and a change through either is visible through both, which is this page's sample. Comparison works the same way: {} === {} is false, because they are two objects, and there is no built-in deep equality. To copy, spread ({ ...a }) or Object.assign({}, a) creates a new object with the same own enumerable properties, but only one level deep, so nested objects are still shared, as below. structuredClone(a), available in browsers and in Node since version 17, makes a deep copy of plain data, including Maps, Sets, Dates and cycles; it throws on functions and turns class instances into plain objects.
const a = { d: { x: 1 } }; const s = { ...a }; const c = structuredClone(a); a.d.x = 9; console.log(s.d.x, c.d.x); // 9 1 console.log({} === {}); // false
2.What is the prototype chain in JavaScript?
In short: Every object has a hidden link to another object, its prototype; reading a missing property follows those links until the property is found or the chain ends at null.
When you read obj.x and obj has no own property x, the engine looks at the object's prototype, then at that object's prototype, and so on. An array's chain runs from the array to Array.prototype to Object.prototype to null, as below, which is how every array has map and every object has toString without storing them. Writing is different: assigning obj.x creates or updates an own property on obj itself, even when x exists further up the chain, and the new property shadows the inherited one. The in operator checks the whole chain, while Object.hasOwn checks only the object itself. Object.create(proto) makes an object with a chosen prototype, and Object.create(null) one with no chain at all.
const up = (o) => Object.getPrototypeOf(o); const arr = [1, 2]; const AP = Array.prototype; console.log(up(arr) === AP); // true const OP = Object.prototype; console.log(up(AP) === OP); // true console.log(up(OP)); // null
3.What is the difference between __proto__ and prototype in JavaScript?
In short: prototype is a property of constructor functions and classes, holding the object their instances will inherit from; __proto__ is a legacy accessor for any object's own prototype link.
When you call new Dog(), the new object's prototype link is set to Dog.prototype, so every instance shares the methods defined there, as bark is below. The link itself is internal, written [[Prototype]] in the specification; Object.getPrototypeOf(obj) reads it and Object.setPrototypeOf changes it, though changing it after creation is slow and rarely needed. __proto__ is an older getter and setter on Object.prototype that exposes the same link, so an instance's __proto__ is its constructor's prototype; it survives for compatibility and is best avoided in new code. Ordinary objects have no prototype property of their own, and arrow functions have none either, one reason they cannot be used with new.
function Dog(n) { this.n = n; } const P = Dog.prototype; P.bark = function () { return this.n + " barks"; }; const d = new Dog("Rex"); console.log(d.bark()); // Rex barks const same = d.__proto__ === P; console.log(same); // true
4.Are JavaScript classes real classes?
In short: They are syntax over prototypes: a class declaration creates a constructor function, puts its methods on the prototype object, and extends links the prototypes, so the inheritance model does not change.
class Animal { speak() {} } produces a function named Animal whose prototype object holds speak, which is what the pre-2015 constructor-function pattern built by hand, and typeof Animal is "function", as below. extends sets up two links, one between the prototype objects for instance methods and one between the constructors for static methods, and super calls the parent's version of a method. Classes do add real rules: they must be called with new, their bodies are strict, their methods are not enumerable, and private #fields are enforced by the language. Knowing both forms helps you read older code, and explains why a class instance's methods are shared rather than copied.
class Animal { speak() { return "..."; } } class Cat extends Animal { speak() { return super.speak() + "!"; } } console.log(typeof Animal); // function console.log(new Cat().speak()); // ...!
5.What is the difference between Object.freeze, Object.seal and const?
In short: const only stops the variable being reassigned; Object.seal stops properties being added or removed; Object.freeze also stops existing properties changing — and both are shallow.
They act at different levels. const is about the binding: the name cannot point at another value, but the object it points at is as mutable as ever. Object.preventExtensions blocks new properties; Object.seal also blocks deleting properties and redefining them, while still allowing their values to change; Object.freeze also makes every property read-only. In strict mode, a forbidden change throws a TypeError; in sloppy mode it fails silently. All three are shallow, so a frozen object's nested objects can still change unless you freeze them too, as the first change below shows. Object.isFrozen and Object.isSealed report the state.
"use strict"; const cfg = Object.freeze({ port: 80, db: { host: "x" }, }); cfg.db.host = "y"; console.log(cfg.db.host); // y cfg.port = 81; // throws TypeError
6.What are getters and setters in JavaScript?
In short: They are accessor properties: reading obj.x runs a get function and assigning to it runs a set function, so a computed or validated value looks like a plain property to callers.
Declared with get and set in an object literal or a class, or with Object.defineProperty, accessors compute a value on demand, such as a full name from first and last names, or validate an assignment, such as rejecting a negative age, without changing how callers use the property. A getter without a setter makes a read-only property: assignment silently does nothing in sloppy mode and throws in strict mode. The usual pairing is a setter guarding a private field, as below. Class accessors live on the prototype, so JSON.stringify leaves them out, while an object literal's getter is an own property and is serialised with its current value.
class User { #age = 0; get age() { return this.#age; } set age(v) { if (v < 0) throw RangeError("age"); this.#age = v; } } const u = new User(); u.age = 21; console.log(u.age); // 21 u.age = -1; // throws RangeError
7.What is the difference between Object.create(null) and {} in JavaScript?
In short: {} inherits from Object.prototype, so it has toString, hasOwnProperty and the __proto__ accessor; Object.create(null) has no prototype at all, which makes it a safe plain dictionary.
An object literal's prototype is Object.prototype, so a lookup for a key that happens to match an inherited name, such as "toString" or "constructor", finds something even though you never stored it, as below. That makes {} a risky dictionary for keys that come from users, and the "__proto__" key can even replace the object's prototype, the root of prototype-pollution bugs. Object.create(null) produces an object with an empty chain: only what you store is there. The cost is that it lacks the usual methods, so it needs Object.keys(obj) or Object.hasOwn(obj, key) rather than obj.hasOwnProperty(key). For most dictionaries, a Map is the better tool, with keys of any type and a size.
const p = {}; const d = Object.create(null); console.log("toString" in p); // true console.log("toString" in d); // false
How the diagnostic asks it
One question from the JavaScript bank, exactly as a sitting would show it. The bank has 3 on objects & prototypes and 30 across JavaScript.
What does this code print?
const a = { n: 1 }; const b = a; const c = { ...a }; b.n = 2; console.log(a.n, c.n);
- 11 1
- 22 2
- 32 1correct
- 41 2
b = a copies the reference, so a and b are the same object and setting b.n changes a.n to 2. { ...a } creates a new object with a copy of a's properties, so c.n keeps its own value, 1. 1 1 assumes b was a copy. 2 2 assumes the spread copy is still linked to a. 1 2 reverses the two.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 JavaScript questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.