JavaScript Classes Interview Questions & Answers

5 interview angles 16 min read source

JavaScript Classes Interview Questions & Answers

JavaScript classes, introduced in ES6, provide a more familiar syntax for creating objects and implementing inheritance. They are syntactic sugar over JavaScript’s existing prototype-based inheritance.

Table of Contents


Class Basics

1. What are classes in JavaScript and how do you create them?

Classes in JavaScript are templates for creating objects. They provide a cleaner syntax for constructor functions and inheritance.

Basic class syntax:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        return `Hello, I'm ${this.name}`;
    }
}

// Creating instances
const person1 = new Person("John", 30);
const person2 = new Person("Jane", 25);

console.log(person1.greet()); // "Hello, I'm John"
console.log(person2.greet()); // "Hello, I'm Jane"

Class declaration vs expression:

// Class declaration
class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }
}

// Class expression
const Circle = class {
    constructor(radius) {
        this.radius = radius;
    }
};

// Named class expression
const Square = class SquareClass {
    constructor(side) {
        this.side = side;
    }
};

2. What is the difference between classes and constructor functions?

Classes are syntactic sugar over constructor functions:

// Constructor function (ES5)
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.greet = function() {
    return `Hello, I'm ${this.name}`;
};

// Class (ES6+)
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        return `Hello, I'm ${this.name}`;
    }
}

Key differences:

Feature Constructor Function Class
Hoisting Hoisted Not hoisted (temporal dead zone)
Syntax Function declaration Class declaration
Methods Added to prototype Defined in class body
Strict mode Not enforced Always in strict mode
Cannot be called without new No Yes (throws error)

Example:

// Constructor function - can be called without new (not recommended)
function Person(name) {
    this.name = name;
}
const person1 = Person("John"); // Works but creates global properties

// Class - must be called with new
class Person {
    constructor(name) {
        this.name = name;
    }
}
// const person2 = Person("John"); // TypeError: Class constructor cannot be invoked without 'new'

3. How do you check if an object is an instance of a class?

Multiple ways to check instance types:

class Animal {
    constructor(name) {
        this.name = name;
    }
}

class Dog extends Animal {
    bark() {
        return "Woof!";
    }
}

const dog = new Dog("Buddy");

// Method 1: instanceof operator
console.log(dog instanceof Dog);    // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

// Method 2: constructor property
console.log(dog.constructor === Dog); // true

// Method 3: Object.prototype.isPrototypeOf
console.log(Dog.prototype.isPrototypeOf(dog)); // true
console.log(Animal.prototype.isPrototypeOf(dog)); // true

// Method 4: getPrototypeOf
console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true

Constructors

4. What is a constructor and how does it work?

Constructor is a special method that is called when creating an instance of a class.

Key characteristics:

  • Must be named constructor
  • Automatically called when using new
  • Used to initialize object properties
  • Can only have one constructor per class
class Car {
    constructor(brand, model, year) {
        this.brand = brand;
        this.model = model;
        this.year = year;
        this.isRunning = false;
    }

    start() {
        this.isRunning = true;
        return `${this.brand} ${this.model} is starting...`;
    }

    stop() {
        this.isRunning = false;
        return `${this.brand} ${this.model} is stopping...`;
    }
}

const myCar = new Car("Toyota", "Camry", 2020);
console.log(myCar.brand);     // "Toyota"
console.log(myCar.isRunning); // false
console.log(myCar.start());   // "Toyota Camry is starting..."

5. What happens if you don’t define a constructor?

If no constructor is defined, JavaScript provides a default constructor:

class SimpleClass {
    // No constructor defined
}

// Equivalent to:
class SimpleClass {
    constructor() {
        // Empty constructor
    }
}

const instance = new SimpleClass(); // Works fine

Default constructor behavior:

  • Takes no parameters
  • Does nothing
  • Calls parent constructor if class extends another class
class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    // No constructor defined
}

// Equivalent to:
class Child extends Parent {
    constructor(...args) {
        super(...args); // Calls parent constructor
    }
}

const child = new Child("John");
console.log(child.name); // "John"

6. How do you call the parent constructor in a child class?

Use the super() keyword to call the parent constructor:

class Animal {
    constructor(name, species) {
        this.name = name;
        this.species = species;
    }

    makeSound() {
        return "Some sound";
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name, "Dog"); // Call parent constructor
        this.breed = breed;
    }

    makeSound() {
        return "Woof!";
    }

    fetch() {
        return `${this.name} is fetching the ball`;
    }
}

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.name);    // "Buddy"
console.log(dog.species); // "Dog"
console.log(dog.breed);   // "Golden Retriever"

Important rules:

  • super() must be called before accessing this
  • Must be called in constructor if parent has constructor
  • Can pass arguments to parent constructor
class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    constructor(name, age) {
        // super(name); // Must call before using this
        this.age = age; // Error if super() not called first
        super(name);    // Correct order
    }
}

Methods

7. What are the different types of methods in classes?

JavaScript classes support several types of methods:

class Calculator {
    // Instance method
    add(a, b) {
        return a + b;
    }

    // Instance method with arrow function
    subtract = (a, b) => {
        return a - b;
    }

    // Static method
    static multiply(a, b) {
        return a * b;
    }

    // Getter method
    get result() {
        return this.lastResult;
    }

    // Setter method
    set result(value) {
        this.lastResult = value;
    }

    // Private method (ES2022+)
    #privateMethod() {
        return "This is private";
    }

    // Public method that uses private method
    publicMethod() {
        return this.#privateMethod();
    }
}

const calc = new Calculator();
console.log(calc.add(5, 3));           // 8
console.log(calc.subtract(10, 4));     // 6
console.log(Calculator.multiply(2, 6)); // 12 (static method)
console.log(calc.publicMethod());      // "This is private"
// console.log(calc.#privateMethod()); // SyntaxError: Private field

8. What are static methods and when would you use them?

Static methods belong to the class itself, not instances. They are called on the class, not objects.

Characteristics:

  • Called on class, not instances
  • Cannot access instance properties/methods
  • Cannot use this keyword
  • Useful for utility functions
class MathUtils {
    static add(a, b) {
        return a + b;
    }

    static multiply(a, b) {
        return a * b;
    }

    static isEven(num) {
        return num % 2 === 0;
    }

    static factorial(n) {
        if (n <= 1) return 1;
        return n * MathUtils.factorial(n - 1);
    }
}

// Calling static methods
console.log(MathUtils.add(5, 3));      // 8
console.log(MathUtils.multiply(4, 6)); // 24
console.log(MathUtils.isEven(10));     // true
console.log(MathUtils.factorial(5));   // 120

// Cannot call on instances
const math = new MathUtils();
// console.log(math.add(5, 3)); // TypeError: math.add is not a function

Common use cases:

class DateUtils {
    static formatDate(date) {
        return date.toLocaleDateString();
    }

    static isToday(date) {
        const today = new Date();
        return date.toDateString() === today.toDateString();
    }

    static getDaysBetween(date1, date2) {
        const diffTime = Math.abs(date2 - date1);
        return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    }
}

class StringUtils {
    static capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    static reverse(str) {
        return str.split('').reverse().join('');
    }

    static isPalindrome(str) {
        const clean = str.toLowerCase().replace(/[^a-z0-9]/g, '');
        return clean === clean.split('').reverse().join('');
    }
}

9. What are getters and setters in classes?

Getters and setters allow you to define computed properties and control access to class properties.

class Circle {
    constructor(radius) {
        this._radius = radius; // Convention: underscore for "private"
    }

    // Getter for radius
    get radius() {
        return this._radius;
    }

    // Setter for radius
    set radius(value) {
        if (value <= 0) {
            throw new Error("Radius must be positive");
        }
        this._radius = value;
    }

    // Getter for diameter (computed property)
    get diameter() {
        return this._radius * 2;
    }

    // Setter for diameter
    set diameter(value) {
        this._radius = value / 2;
    }

    // Getter for area (computed property)
    get area() {
        return Math.PI * this._radius ** 2;
    }

    // Getter for circumference (computed property)
    get circumference() {
        return 2 * Math.PI * this._radius;
    }
}

const circle = new Circle(5);
console.log(circle.radius);        // 5
console.log(circle.diameter);      // 10
console.log(circle.area);          // 78.54...
console.log(circle.circumference); // 31.41...

circle.radius = 10;                // Uses setter
console.log(circle.diameter);      // 20
console.log(circle.area);          // 314.15...

// circle.radius = -5;             // Error: Radius must be positive

Validation and computed properties:

class BankAccount {
    constructor(initialBalance) {
        this._balance = initialBalance;
        this._transactions = [];
    }

    get balance() {
        return this._balance;
    }

    set balance(value) {
        throw new Error("Cannot directly set balance. Use deposit() or withdraw()");
    }

    get transactions() {
        return [...this._transactions]; // Return copy to prevent external modification
    }

    get isOverdrawn() {
        return this._balance < 0;
    }

    deposit(amount) {
        if (amount <= 0) {
            throw new Error("Deposit amount must be positive");
        }
        this._balance += amount;
        this._transactions.push({ type: 'deposit', amount, date: new Date() });
    }

    withdraw(amount) {
        if (amount <= 0) {
            throw new Error("Withdrawal amount must be positive");
        }
        if (amount > this._balance) {
            throw new Error("Insufficient funds");
        }
        this._balance -= amount;
        this._transactions.push({ type: 'withdrawal', amount, date: new Date() });
    }
}

Inheritance

10. How does inheritance work in JavaScript classes?

Inheritance allows a class to inherit properties and methods from another class using the extends keyword.

class Animal {
    constructor(name, species) {
        this.name = name;
        this.species = species;
    }

    makeSound() {
        return "Some sound";
    }

    getInfo() {
        return `${this.name} is a ${this.species}`;
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name, "Dog");
        this.breed = breed;
    }

    makeSound() {
        return "Woof!";
    }

    fetch() {
        return `${this.name} is fetching the ball`;
    }
}

class Cat extends Animal {
    constructor(name, color) {
        super(name, "Cat");
        this.color = color;
    }

    makeSound() {
        return "Meow!";
    }

    climb() {
        return `${this.name} is climbing the tree`;
    }
}

const dog = new Dog("Buddy", "Golden Retriever");
const cat = new Cat("Whiskers", "Orange");

console.log(dog.getInfo());    // "Buddy is a Dog"
console.log(dog.makeSound());  // "Woof!"
console.log(dog.fetch());      // "Buddy is fetching the ball"

console.log(cat.getInfo());    // "Whiskers is a Cat"
console.log(cat.makeSound());  // "Meow!"
console.log(cat.climb());      // "Whiskers is climbing the tree"

11. What is method overriding and how does it work?

Method overriding occurs when a child class defines a method with the same name as a parent class method.

class Vehicle {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }

    start() {
        return `${this.make} ${this.model} is starting`;
    }

    stop() {
        return `${this.make} ${this.model} is stopping`;
    }

    getInfo() {
        return `${this.make} ${this.model}`;
    }
}

class Car extends Vehicle {
    constructor(make, model, fuelType) {
        super(make, model);
        this.fuelType = fuelType;
    }

    // Override the start method
    start() {
        return `${super.start()} with ${this.fuelType} engine`;
    }

    // Override the getInfo method
    getInfo() {
        return `${super.getInfo()} (${this.fuelType})`;
    }

    // New method specific to Car
    honk() {
        return "Beep beep!";
    }
}

class ElectricCar extends Car {
    constructor(make, model, batteryCapacity) {
        super(make, model, "Electric");
        this.batteryCapacity = batteryCapacity;
    }

    // Override the start method again
    start() {
        return `${this.make} ${this.model} is starting silently`;
    }

    // New method specific to ElectricCar
    charge() {
        return `${this.make} ${this.model} is charging`;
    }
}

const car = new Car("Toyota", "Camry", "Gasoline");
const electricCar = new ElectricCar("Tesla", "Model 3", "75kWh");

console.log(car.start());        // "Toyota Camry is starting with Gasoline engine"
console.log(car.getInfo());      // "Toyota Camry (Gasoline)"
console.log(car.honk());         // "Beep beep!"

console.log(electricCar.start()); // "Tesla Model 3 is starting silently"
console.log(electricCar.getInfo()); // "Tesla Model 3 (Electric)"
console.log(electricCar.charge()); // "Tesla Model 3 is charging"

12. How do you check the inheritance chain?

Multiple ways to check inheritance relationships:

class Animal {
    constructor(name) {
        this.name = name;
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }
}

class GoldenRetriever extends Dog {
    constructor(name) {
        super(name, "Golden Retriever");
    }
}

const dog = new GoldenRetriever("Buddy");

// Method 1: instanceof operator
console.log(dog instanceof GoldenRetriever); // true
console.log(dog instanceof Dog);             // true
console.log(dog instanceof Animal);          // true
console.log(dog instanceof Object);          // true

// Method 2: constructor property
console.log(dog.constructor === GoldenRetriever); // true
console.log(dog.constructor === Dog);             // false

// Method 3: isPrototypeOf
console.log(GoldenRetriever.prototype.isPrototypeOf(dog)); // true
console.log(Dog.prototype.isPrototypeOf(dog));             // true
console.log(Animal.prototype.isPrototypeOf(dog));          // true

// Method 4: getPrototypeOf chain
let current = Object.getPrototypeOf(dog);
while (current) {
    console.log(current.constructor.name);
    current = Object.getPrototypeOf(current);
}
// Output: GoldenRetriever, Dog, Animal, Object

Static Members

13. What are static properties and how do you use them?

Static properties belong to the class itself, not instances.

class Database {
    static connectionCount = 0;
    static maxConnections = 10;

    constructor() {
        if (Database.connectionCount >= Database.maxConnections) {
            throw new Error("Maximum connections reached");
        }
        Database.connectionCount++;
        this.id = Database.connectionCount;
    }

    static getConnectionCount() {
        return Database.connectionCount;
    }

    static resetConnections() {
        Database.connectionCount = 0;
    }

    disconnect() {
        Database.connectionCount--;
        return `Connection ${this.id} disconnected`;
    }
}

// Using static properties
console.log(Database.connectionCount);    // 0
console.log(Database.maxConnections);     // 10

const db1 = new Database();
const db2 = new Database();

console.log(Database.getConnectionCount()); // 2
console.log(db1.disconnect());             // "Connection 1 disconnected"
console.log(Database.getConnectionCount()); // 1

Static properties with inheritance:

class Vehicle {
    static vehicleCount = 0;

    constructor() {
        Vehicle.vehicleCount++;
    }

    static getCount() {
        return Vehicle.vehicleCount;
    }
}

class Car extends Vehicle {
    static carCount = 0;

    constructor() {
        super();
        Car.carCount++;
    }

    static getCarCount() {
        return Car.carCount;
    }
}

const car1 = new Car();
const car2 = new Car();
const vehicle = new Vehicle();

console.log(Vehicle.getCount());    // 3 (total vehicles)
console.log(Car.getCarCount());     // 2 (only cars)
console.log(Car.getCount());        // 3 (inherited static method)

Private Fields

14. What are private fields and how do you use them?

Private fields (ES2022+) allow you to create truly private properties that cannot be accessed outside the class.

class BankAccount {
    #balance = 0;           // Private field
    #accountNumber;         // Private field declaration
    #transactions = [];     // Private field

    constructor(accountNumber, initialBalance = 0) {
        this.#accountNumber = accountNumber;
        this.#balance = initialBalance;
    }

    // Public method to access private field
    getBalance() {
        return this.#balance;
    }

    // Public method to access private field
    getAccountNumber() {
        return this.#accountNumber;
    }

    deposit(amount) {
        if (amount <= 0) {
            throw new Error("Deposit amount must be positive");
        }
        this.#balance += amount;
        this.#transactions.push({
            type: 'deposit',
            amount,
            date: new Date()
        });
    }

    withdraw(amount) {
        if (amount <= 0) {
            throw new Error("Withdrawal amount must be positive");
        }
        if (amount > this.#balance) {
            throw new Error("Insufficient funds");
        }
        this.#balance -= amount;
        this.#transactions.push({
            type: 'withdrawal',
            amount,
            date: new Date()
        });
    }

    // Private method
    #validateAmount(amount) {
        return amount > 0 && Number.isFinite(amount);
    }

    // Public method using private method
    transfer(amount, targetAccount) {
        if (!this.#validateAmount(amount)) {
            throw new Error("Invalid amount");
        }
        this.withdraw(amount);
        targetAccount.deposit(amount);
    }
}

const account1 = new BankAccount("12345", 1000);
const account2 = new BankAccount("67890", 500);

console.log(account1.getBalance()); // 1000
// console.log(account1.#balance); // SyntaxError: Private field

account1.transfer(200, account2);
console.log(account1.getBalance()); // 800
console.log(account2.getBalance()); // 700

Private fields with inheritance:

class Animal {
    #name;

    constructor(name) {
        this.#name = name;
    }

    getName() {
        return this.#name;
    }
}

class Dog extends Animal {
    #breed;

    constructor(name, breed) {
        super(name);
        this.#breed = breed;
    }

    getBreed() {
        return this.#breed;
    }

    getInfo() {
        return `${this.getName()} is a ${this.#breed}`;
    }
}

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.getName());  // "Buddy"
console.log(dog.getBreed()); // "Golden Retriever"
console.log(dog.getInfo());  // "Buddy is a Golden Retriever"

// console.log(dog.#name);   // SyntaxError: Private field
// console.log(dog.#breed);  // SyntaxError: Private field

Advanced Class Concepts

15. What are abstract classes and how can you simulate them?

JavaScript doesn’t have built-in abstract classes, but you can simulate them:

class AbstractVehicle {
    constructor() {
        if (this.constructor === AbstractVehicle) {
            throw new Error("AbstractVehicle cannot be instantiated directly");
        }

        if (!this.start) {
            throw new Error("Subclasses must implement start() method");
        }

        if (!this.stop) {
            throw new Error("Subclasses must implement stop() method");
        }
    }

    // Abstract method (must be implemented by subclasses)
    start() {
        throw new Error("start() method must be implemented");
    }

    stop() {
        throw new Error("stop() method must be implemented");
    }

    // Concrete method (can be used by all subclasses)
    getInfo() {
        return `${this.constructor.name}`;
    }
}

class Car extends AbstractVehicle {
    constructor(make, model) {
        super();
        this.make = make;
        this.model = model;
    }

    start() {
        return `${this.make} ${this.model} is starting`;
    }

    stop() {
        return `${this.make} ${this.model} is stopping`;
    }

    getInfo() {
        return `${super.getInfo()}: ${this.make} ${this.model}`;
    }
}

class Motorcycle extends AbstractVehicle {
    constructor(brand, type) {
        super();
        this.brand = brand;
        this.type = type;
    }

    start() {
        return `${this.brand} ${this.type} is starting`;
    }

    stop() {
        return `${this.brand} ${this.type} is stopping`;
    }
}

// const vehicle = new AbstractVehicle(); // Error: AbstractVehicle cannot be instantiated directly

const car = new Car("Toyota", "Camry");
const motorcycle = new Motorcycle("Honda", "Sport");

console.log(car.start());        // "Toyota Camry is starting"
console.log(car.getInfo());      // "Car: Toyota Camry"
console.log(motorcycle.start()); // "Honda Sport is starting"

16. How do you implement the Singleton pattern with classes?

Singleton pattern ensures a class has only one instance and provides global access to it.

class Database {
    static #instance = null;
    #connectionString;
    #isConnected = false;

    constructor(connectionString) {
        if (Database.#instance) {
            return Database.#instance;
        }

        this.#connectionString = connectionString;
        Database.#instance = this;
    }

    static getInstance(connectionString = null) {
        if (!Database.#instance) {
            Database.#instance = new Database(connectionString);
        }
        return Database.#instance;
    }

    connect() {
        if (this.#isConnected) {
            return "Already connected";
        }
        this.#isConnected = true;
        return `Connected to ${this.#connectionString}`;
    }

    disconnect() {
        if (!this.#isConnected) {
            return "Not connected";
        }
        this.#isConnected = false;
        return "Disconnected";
    }

    query(sql) {
        if (!this.#isConnected) {
            throw new Error("Not connected to database");
        }
        return `Executing: ${sql}`;
    }
}

// Usage
const db1 = Database.getInstance("mysql://localhost:3306/mydb");
const db2 = Database.getInstance("postgres://localhost:5432/mydb");

console.log(db1 === db2); // true (same instance)

console.log(db1.connect()); // "Connected to mysql://localhost:3306/mydb"
console.log(db2.connect()); // "Already connected"

console.log(db1.query("SELECT * FROM users")); // "Executing: SELECT * FROM users"

17. How do you implement the Factory pattern with classes?

Factory pattern creates objects without specifying their exact class.

class Vehicle {
    constructor(type, make, model) {
        this.type = type;
        this.make = make;
        this.model = model;
    }

    getInfo() {
        return `${this.make} ${this.model} (${this.type})`;
    }
}

class VehicleFactory {
    static createVehicle(type, make, model) {
        switch (type.toLowerCase()) {
            case 'car':
                return new Car(make, model);
            case 'motorcycle':
                return new Motorcycle(make, model);
            case 'truck':
                return new Truck(make, model);
            default:
                throw new Error(`Unknown vehicle type: ${type}`);
        }
    }

    static createCar(make, model) {
        return new Car(make, model);
    }

    static createMotorcycle(make, model) {
        return new Motorcycle(make, model);
    }

    static createTruck(make, model) {
        return new Truck(make, model);
    }
}

class Car extends Vehicle {
    constructor(make, model) {
        super('Car', make, model);
    }

    drive() {
        return `${this.getInfo()} is driving on the road`;
    }
}

class Motorcycle extends Vehicle {
    constructor(make, model) {
        super('Motorcycle', make, model);
    }

    ride() {
        return `${this.getInfo()} is riding on the road`;
    }
}

class Truck extends Vehicle {
    constructor(make, model) {
        super('Truck', make, model);
    }

    haul() {
        return `${this.getInfo()} is hauling cargo`;
    }
}

// Usage
const car = VehicleFactory.createVehicle('car', 'Toyota', 'Camry');
const motorcycle = VehicleFactory.createMotorcycle('Honda', 'CBR');
const truck = VehicleFactory.createTruck('Ford', 'F-150');

console.log(car.drive());      // "Toyota Camry (Car) is driving on the road"
console.log(motorcycle.ride()); // "Honda CBR (Motorcycle) is riding on the road"
console.log(truck.haul());     // "Ford F-150 (Truck) is hauling cargo"

18. How do you implement mixins with classes?

Mixins are a way to share functionality between classes without inheritance.

// Mixin functions
const Swimmable = (superclass) => class extends superclass {
    swim() {
        return `${this.name} is swimming`;
    }
};

const Flyable = (superclass) => class extends superclass {
    fly() {
        return `${this.name} is flying`;
    }
};

const Walkable = (superclass) => class extends superclass {
    walk() {
        return `${this.name} is walking`;
    }
};

// Base class
class Animal {
    constructor(name) {
        this.name = name;
    }

    makeSound() {
        return "Some sound";
    }
}

// Classes with mixins
class Duck extends Swimmable(Flyable(Walkable(Animal))) {
    constructor(name) {
        super(name);
    }

    makeSound() {
        return "Quack!";
    }
}

class Fish extends Swimmable(Animal) {
    constructor(name) {
        super(name);
    }

    makeSound() {
        return "Blub blub!";
    }
}

class Bird extends Flyable(Walkable(Animal)) {
    constructor(name) {
        super(name);
    }

    makeSound() {
        return "Tweet!";
    }
}

// Usage
const duck = new Duck("Donald");
const fish = new Fish("Nemo");
const bird = new Bird("Tweety");

console.log(duck.swim());   // "Donald is swimming"
console.log(duck.fly());    // "Donald is flying"
console.log(duck.walk());   // "Donald is walking"
console.log(duck.makeSound()); // "Quack!"

console.log(fish.swim());   // "Nemo is swimming"
// console.log(fish.fly()); // Error: fish.fly is not a function

console.log(bird.fly());    // "Tweety is flying"
console.log(bird.walk());   // "Tweety is walking"

Additional Resources


JavaScript classes provide a clean and familiar syntax for object-oriented programming. Understanding classes is essential for modern JavaScript development.

Interview angle

  • “Are JS classes real classes?” - no, they are syntax over prototypal inheritance. class creates a constructor function whose prototype holds the methods; extends sets up the prototype chain. Knowing this explains why methods are shared and why this behaves as it does.
  • “Why does this break in an extracted method?” - this is determined by the call site, not the definition. Passing obj.method as a callback loses the receiver. Bind it, wrap it in an arrow, or define the method as a class field with an arrow function.
  • “How do you make a field genuinely private?” - #name. It is enforced by the language, unlike the _name convention. Accessing #name from outside the class is a syntax error, not a runtime undefined.
  • “What is a static block for?” - class-level initialisation that needs statements, running once at class definition. Useful for registration and for computing static state from several fields.
  • “What must a subclass constructor do?” - call super() before touching this. Skipping it throws, because the base constructor is what allocates this.