JavaScript Fundamentals: Prototype Chains and How 'this' Is Bound

Read the full interview experience this question came from →

Quick Overview

A JavaScript fundamentals question that asks you to predict and explain how this is bound in method calls, detached calls, bound functions and arrow functions, trace property lookup through a prototype chain, and rewrite constructor-based inheritance with class syntax. It tests precise reasoning about core language behaviour.

JavaScript Fundamentals: Prototype Chains and How 'this' Is Bound

Company: HubSpot

Role: Frontend Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Onsite

This round of a frontend-focused interview track targets core JavaScript language behaviour rather than any framework: how prototypes and inheritance work, how the value of `this` is decided in different call contexts, and reasoning precisely about what a piece of code does. The snippets used in the interview are not reproduced here; the parts below rebuild those topics as practice. Work through each part without running the code, and explain every prediction. ### Constraints and Clarifications - Assume modern JavaScript (ES2015 or later). The snippets declare strict mode, and class bodies are always strict. - Predictions must come with the rule that produces them; a correct output with no reason earns little credit. ### Clarifying Questions - Is the code running in strict mode or sloppy mode, and as a module or a classic script? - Is it running in a browser or in Node.js, given that the global object differs? - May I run the code, or should I reason it through? - Which language features may I use in rewrites, for example class fields? ### Part 1 — Predict how `this` is bound ```javascript 'use strict'; const counter = { count: 0, increment() { this.count += 1; return this.count; }, reader() { return () => this.count; }, }; const detached = counter.increment; const boundToOther = counter.increment.bind({ count: 10 }); const read = counter.reader(); console.log(counter.increment()); // (1) console.log(boundToOther()); // (2) console.log(boundToOther.call({ count: 100 })); // (3) console.log(read.call({ count: 50 })); // (4) try { detached(); console.log('no error'); } catch (err) { console.log(err instanceof TypeError); // (5) } ``` Predict what each numbered line prints. Then state the rules that decide `this`, in order of precedence. ```hint Read each line as a call For every numbered line, write down exactly how the function is being invoked at that moment and what, if anything, sits to the left of the dot. ``` #### What This Part Should Cover - The correct output for every line, each with its reason - The precedence between construction, explicit binding, method calls and plain calls - How arrow functions get their `this` - How strict versus sloppy mode changes the plain-call case ### Part 2 — Trace the prototype chain ```javascript function Animal(name) { this.name = name; } Animal.prototype.speak = function () { return `${this.name} makes a sound`; }; function Dog(name) { Animal.call(this, name); } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.speak = function () { return `${this.name} barks`; }; const rex = new Dog('Rex'); console.log(rex.speak()); // (a) console.log(Object.getPrototypeOf(rex) === Dog.prototype); // (b) console.log(rex instanceof Animal); // (c) console.log(rex.hasOwnProperty('speak')); // (d) delete Dog.prototype.speak; console.log(rex.speak()); // (e) Animal.prototype.speak = function () { return 'changed'; }; console.log(rex.speak()); // (f) ``` Predict each lettered line, then explain step by step what `new Dog('Rex')` does and why the three lines after the `Dog` function are written the way they are. ```hint Walk the links For each property access, list the objects the engine checks, in order, until it finds the name. ``` #### What This Part Should Cover - Correct outputs, each with the lookup path that produced it - The difference between own and inherited properties - The fact that prototypes are shared objects whose changes reach existing instances - The steps `new` performs, and the purpose of each setup line ### Part 3 — Rewrite with class syntax and fix a lost `this` Rewrite `Animal` and `Dog` with `class`, `extends` and `super`, keeping the same behaviour. Explain what stays the same underneath and what genuinely differs from the function-based version. Then a teammate writes the helper below and calls `runLater(rex.speak)` on an instance of your class-based `Dog`. It throws a `TypeError`. ```javascript function runLater(fn) { return fn(); } ``` Explain why it throws, and give two different fixes with the trade-off between them. ```hint Same machinery, new syntax Compare the objects and links each version creates, then look for behaviour that the class version adds and the function version cannot express. ``` #### What This Part Should Cover - A correct class rewrite using `extends` and `super` - The real differences between class and function-based inheritance - A diagnosis of the `TypeError` and two fixes with their trade-off ### What a Strong Answer Covers - Predictions derived from rules rather than remembered outputs - One connected mental model: a method found on a prototype still runs with the receiver of the call as `this` - Awareness of environment differences such as strict versus sloppy mode and browser versus Node.js - Precise vocabulary: a function's `prototype` property versus an object's prototype link, own properties, the receiver - Clear thinking out loud while reasoning about each line ### Follow-up Questions - What does `new` return when a constructor explicitly returns an object, and when it returns a primitive? - Implement a simplified `bind` yourself. How should it behave when its result is called with `new`? - Why can arrow functions not be used as constructors, and what else do they lack compared with regular functions? - What changes if `speak` is declared as an arrow-function class field instead of a method, in terms of memory use and inheritance?

Overview: A JavaScript fundamentals question that asks you to predict and explain how this is bound in method calls, detached calls, bound functions and arrow functions, trace property lookup through a prototype chain, and rewrite constructor-based inheritance with class syntax. It tests precise reasoning about core language behaviour.

Read the full HubSpot Frontend Engineer interview experience this question came from

HubSpot logo
HubSpot
Jan 2, 2026
mediumFrontend EngineerOnsiteSoftware Engineering Fundamentals
0
0

This round of a frontend-focused interview track targets core JavaScript language behaviour rather than any framework: how prototypes and inheritance work, how the value of this is decided in different call contexts, and reasoning precisely about what a piece of code does. The snippets used in the interview are not reproduced here; the parts below rebuild those topics as practice. Work through each part without running the code, and explain every prediction.

Constraints and Clarifications

  • Assume modern JavaScript (ES2015 or later). The snippets declare strict mode, and class bodies are always strict.
  • Predictions must come with the rule that produces them; a correct output with no reason earns little credit.

Clarifying Questions Guidance

  • Is the code running in strict mode or sloppy mode, and as a module or a classic script?
  • Is it running in a browser or in Node.js, given that the global object differs?
  • May I run the code, or should I reason it through?
  • Which language features may I use in rewrites, for example class fields?

Part 1 — Predict how this is bound

'use strict';

const counter = {
  count: 0,
  increment() {
    this.count += 1;
    return this.count;
  },
  reader() {
    return () => this.count;
  },
};

const detached = counter.increment;
const boundToOther = counter.increment.bind({ count: 10 });
const read = counter.reader();

console.log(counter.increment());               // (1)
console.log(boundToOther());                    // (2)
console.log(boundToOther.call({ count: 100 })); // (3)
console.log(read.call({ count: 50 }));          // (4)

try {
  detached();
  console.log('no error');
} catch (err) {
  console.log(err instanceof TypeError);        // (5)
}

Predict what each numbered line prints. Then state the rules that decide this, in order of precedence.

What This Part Should Cover Guidance

  • The correct output for every line, each with its reason
  • The precedence between construction, explicit binding, method calls and plain calls
  • How arrow functions get their this
  • How strict versus sloppy mode changes the plain-call case

Part 2 — Trace the prototype chain

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return `${this.name} makes a sound`;
};

function Dog(name) {
  Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () {
  return `${this.name} barks`;
};

const rex = new Dog('Rex');
console.log(rex.speak());                                  // (a)
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // (b)
console.log(rex instanceof Animal);                        // (c)
console.log(rex.hasOwnProperty('speak'));                  // (d)

delete Dog.prototype.speak;
console.log(rex.speak());                                  // (e)

Animal.prototype.speak = function () {
  return 'changed';
};
console.log(rex.speak());                                  // (f)

Predict each lettered line, then explain step by step what new Dog('Rex') does and why the three lines after the Dog function are written the way they are.

What This Part Should Cover Guidance

  • Correct outputs, each with the lookup path that produced it
  • The difference between own and inherited properties
  • The fact that prototypes are shared objects whose changes reach existing instances
  • The steps new performs, and the purpose of each setup line

Part 3 — Rewrite with class syntax and fix a lost this

Rewrite Animal and Dog with class, extends and super, keeping the same behaviour. Explain what stays the same underneath and what genuinely differs from the function-based version.

Then a teammate writes the helper below and calls runLater(rex.speak) on an instance of your class-based Dog. It throws a TypeError.

function runLater(fn) {
  return fn();
}

Explain why it throws, and give two different fixes with the trade-off between them.

What This Part Should Cover Guidance

  • A correct class rewrite using extends and super
  • The real differences between class and function-based inheritance
  • A diagnosis of the TypeError and two fixes with their trade-off

What a Strong Answer Covers Guidance

  • Predictions derived from rules rather than remembered outputs
  • One connected mental model: a method found on a prototype still runs with the receiver of the call as this
  • Awareness of environment differences such as strict versus sloppy mode and browser versus Node.js
  • Precise vocabulary: a function's prototype property versus an object's prototype link, own properties, the receiver
  • Clear thinking out loud while reasoning about each line

Follow-up Questions Guidance

  • What does new return when a constructor explicitly returns an object, and when it returns a primitive?
  • Implement a simplified bind yourself. How should it behave when its result is called with new ?
  • Why can arrow functions not be used as constructors, and what else do they lack compared with regular functions?
  • What changes if speak is declared as an arrow-function class field instead of a method, in terms of memory use and inheritance?