JavaScript Interview Prep
Prototypes & Inheritance

ES6 Classes

Syntactic Sugar Over Prototypes

LinkedIn Hook

Most JavaScript developers think class brought "real" classes to the language.

It didn't.

Every class you write is compiled down to exactly the same prototype pattern we've been using since 1995 — a constructor function, methods hung on .prototype, and Object.create linking parent and child chains.

class Dog extends Animal is literally Dog.prototype = Object.create(Animal.prototype) plus some safety rules.

But classes DO add genuinely new things: non-enumerable methods, forced strict mode, no hoisting, must-use-new, super, and finally — truly private fields with #.

In this lesson you'll see class syntax and prototype syntax side-by-side, learn what super() actually compiles to, and understand the 4 real differences between classes and plain constructor functions.

After this, when someone asks "are JavaScript classes real classes?" you'll give the answer that impresses senior engineers.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #ES6 #Classes #Prototypes #Frontend #CodingInterview #OOP


ES6 Classes thumbnail


What You'll Learn

  • How class, constructor, extends, and super map directly to prototype patterns
  • Where static methods live and why they don't exist on instances
  • How # private fields, non-enumerable methods, and strict mode differentiate classes from plain functions

Classes Are Functions in Disguise

ES6 classes look like classical OOP — but under the hood, they're just functions and prototypes. The class keyword is syntactic sugar over JavaScript's existing prototype-based inheritance.

Let's prove it.

Class Syntax vs Prototype Syntax — Side by Side

// ============= ES6 CLASS =============
class PersonClass {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return "Hi, I'm " + this.name;
  }

  static create(name, age) {
    return new PersonClass(name, age);
  }
}

// ============= EQUIVALENT PROTOTYPE =============
function PersonFunc(name, age) {
  this.name = name;
  this.age = age;
}

PersonFunc.prototype.greet = function () {
  return "Hi, I'm " + this.name;
};

// static method = property on the constructor itself
PersonFunc.create = function (name, age) {
  return new PersonFunc(name, age);
};

// They work identically:
const p1 = new PersonClass("Rakibul", 25);
const p2 = new PersonFunc("Rakibul", 25);

console.log(p1.greet()); // "Hi, I'm Rakibul"
console.log(p2.greet()); // "Hi, I'm Rakibul"

// Proof that class is just a function:
console.log(typeof PersonClass); // "function"
console.log(PersonClass.prototype.constructor === PersonClass); // true

Inheritance — extends & super

// ============= ES6 CLASS =============
class Animal {
  constructor(name) {
    this.name = name;
  }
  eat() {
    return this.name + " is eating";
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // calls Animal's constructor
    this.breed = breed;
  }
  bark() {
    return this.name + " says Woof!";
  }
}

// ============= EQUIVALENT PROTOTYPE =============
function AnimalFunc(name) {
  this.name = name;
}
AnimalFunc.prototype.eat = function () {
  return this.name + " is eating";
};

function DogFunc(name, breed) {
  AnimalFunc.call(this, name); // super(name) equivalent
  this.breed = breed;
}
DogFunc.prototype = Object.create(AnimalFunc.prototype);
DogFunc.prototype.constructor = DogFunc;
DogFunc.prototype.bark = function () {
  return this.name + " says Woof!";
};

// Both produce identical prototype chains:
const d1 = new Dog("Rex", "Shepherd");
const d2 = new DogFunc("Rex", "Shepherd");

console.log(d1.bark()); // "Rex says Woof!"
console.log(d1.eat());  // "Rex is eating"
console.log(d1 instanceof Dog);    // true
console.log(d1 instanceof Animal); // true

Static Methods

class MathUtils {
  static add(a, b) {
    return a + b;
  }
  static multiply(a, b) {
    return a * b;
  }
}

// Static methods live on the constructor, NOT the prototype
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.prototype.add); // undefined

// You cannot call static methods on instances
const m = new MathUtils();
// m.add(2, 3); // TypeError: m.add is not a function

// Under the hood, it's just:
// MathUtils.add = function(a, b) { return a + b; };

Private Fields (#)

class BankAccount {
  #balance; // private field — cannot be accessed outside the class
  #pin;

  constructor(owner, initialBalance, pin) {
    this.owner = owner;
    this.#balance = initialBalance;
    this.#pin = pin;
  }

  #validatePin(pin) { // private method
    return pin === this.#pin;
  }

  deposit(amount) {
    this.#balance += amount;
    return this.#balance;
  }

  withdraw(amount, pin) {
    if (!this.#validatePin(pin)) {
      throw new Error("Invalid PIN");
    }
    if (amount > this.#balance) {
      throw new Error("Insufficient funds");
    }
    this.#balance -= amount;
    return this.#balance;
  }

  getBalance(pin) {
    if (!this.#validatePin(pin)) {
      throw new Error("Invalid PIN");
    }
    return this.#balance;
  }
}

const account = new BankAccount("Rakibul", 1000, 1234);

console.log(account.owner);       // "Rakibul" (public)
console.log(account.deposit(500)); // 1500
console.log(account.getBalance(1234)); // 1500

// These all fail:
// console.log(account.#balance); // SyntaxError: Private field
// console.log(account.#pin);     // SyntaxError: Private field
// account.#validatePin(1234);    // SyntaxError: Private field

Key Differences Between Classes and Plain Functions

class Example {
  constructor() {}
}

// 1. Classes are NOT hoisted (unlike function declarations)
// const e = new MyClass(); // ReferenceError
// class MyClass {}

// 2. Classes always run in strict mode
class StrictCheck {
  method() {
    // 'this' is undefined if method is called without context
    // (not window, because strict mode)
    return this;
  }
}
const fn = new StrictCheck().method;
console.log(fn()); // undefined (strict mode, not window)

// 3. Classes cannot be called without 'new'
// Example(); // TypeError: Class constructor Example cannot be invoked without 'new'

// 4. Class methods are non-enumerable
console.log(Object.keys(Example.prototype)); // [] (methods don't show up)
// With prototype syntax, they ARE enumerable:
function Func() {}
Func.prototype.method = function () {};
console.log(Object.keys(Func.prototype)); // ["method"]

ES6 Classes visual 1


Common Mistakes

  • Thinking class creates real classes — it creates a function. typeof MyClass === "function", and methods live on MyClass.prototype.
  • Forgetting to call super() before using this in a subclass constructor — the spec requires super() first, otherwise you get a ReferenceError.
  • Calling a class without new (e.g. Example() instead of new Example()) — unlike plain constructor functions, classes throw TypeError when invoked without new.

Interview Questions

Q: Are JavaScript classes real classes?

No. JavaScript classes are syntactic sugar over prototype-based inheritance. class creates a constructor function, methods go on the prototype, extends sets up the prototype chain, and super calls the parent constructor. Under the hood, it's all prototypes.

Q: What does super() do in a constructor?

super() calls the parent class's constructor. It's equivalent to ParentConstructor.call(this, args). In a derived class, you MUST call super() before using this, or you'll get a ReferenceError.

Q: What are private fields in JavaScript?

Private fields (prefixed with #) are class properties that cannot be accessed or modified outside the class. They're enforced at the language level — not by convention (like the _prefix pattern). Accessing them outside the class throws a SyntaxError.

Q: Can you name 3 differences between class and function constructors?

  1. Classes are not hoisted — you can't use them before declaration. 2) Classes always run in strict mode. 3) Class methods are non-enumerable. 4) Classes must be called with new — calling without it throws TypeError.

Q: What is a static method and where does it live?

A static method is attached to the constructor itself, not to .prototype. It's called as ClassName.method() and is not accessible on instances (instance.method() is undefined). Under the hood, static method(){} is equivalent to ClassName.method = function(){}.


Quick Reference — Cheat Sheet

ES6 CLASSES = SYNTACTIC SUGAR

class              -> function (typeof === "function")
constructor        -> function body
methods            -> on Class.prototype (non-enumerable)
static method      -> property on the constructor itself
extends Parent     -> Object.create(Parent.prototype) chain
super(args)        -> Parent.call(this, args)
#privateField      -> true private (language-enforced, SyntaxError on leak)

Class-only rules:
  - NOT hoisted (TDZ until declaration)
  - ALWAYS runs in strict mode
  - MUST be called with `new` (else TypeError)
  - Methods are NON-enumerable (Object.keys returns [])
  - super() must be called before `this` in derived constructor

Previous: Object.create() Next: instanceof & typeof


This is Lesson 5.4 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page