Understanding Inheritance in JavaScript

What is Inheritance?

Inheritance is a fundamental concept in object-oriented programming. It allows one class (a subclass or child class) to acquire the properties and methods of another class (a superclass or parent class). By doing so, subclasses can reuse and build upon the existing functionality, reducing code duplication and improving maintainability.

Inheritance in JavaScript (ES6 Classes)

Prior to ES6, inheritance in JavaScript was handled through prototypes and constructor functions. ES6 introduced the class syntax, which provides a more familiar, class-based approach to inheritance, although under the hood it is still prototype-based.

Key Keywords for Inheritance:

  • extends: Used in a class declaration or class expression to create a subclass.
  • super: Used to call the parent class’s constructor or parent methods.

The extends Keyword

When you write class Child extends Parent, you are creating a subclass Child that inherits all the methods and properties of the Parent class. The Child class’s prototype is linked to Parent.prototype, enabling instances of Child to have access to Parent’s instance methods.

Example:

class Animal {

  eat() {

    console.log(“The animal is eating.”);

  }

}

class Dog extends Animal {

  bark() {

    console.log(“Woof!”);

  }

}

const d = new Dog();

d.eat(); // “The animal is eating.”

d.bark(); // “Woof!”

The super Keyword

super serves two purposes in subclassing:

  1. Calling the Superclass Constructor:
    In a subclass constructor, before you can use this, you must call super() to ensure the parent’s initialization logic runs. If you omit super(), this is not defined.
  2. Calling Superclass Methods:
    Inside subclass methods, you can call super.methodName() to invoke a method on the parent class. This allows for extending or modifying behavior rather than completely replacing it.

Example Calling Super Constructor:

class Animal {

  constructor(name) {

    this.name = name;

  }

  eat() {

    console.log(this.name + ” is eating.”);

  }

}

class Dog extends Animal {

  constructor(name, breed) {

    super(name); // calls Animal’s constructor

    this.breed = breed;

  }

  bark() {

    console.log(this.name + ” says woof!”);

  }

}

const dog = new Dog(“Rex”, “Labrador”);

dog.eat(); // “Rex is eating.”

dog.bark(); // “Rex says woof!”

Example Calling Super Method:

class Animal {

  speak() {

    console.log(“The animal makes a sound.”);

  }

}

class Cat extends Animal {

  speak() {

    super.speak(); // call parent speak

    console.log(“The cat meows.”);

  }

}

const cat = new Cat();

cat.speak();

// “The animal makes a sound.”

// “The cat meows.”

Subclassing Built-in Objects

You can also subclass built-in objects like Array or Error. This can be useful but may have subtleties depending on browser implementations.

Example Subclassing Array:

class MyArray extends Array {

  first() {

    return this[0];

  }

}

const arr = new MyArray(1, 2, 3);

console.log(arr.first()); // 1

console.log(arr instanceof MyArray); // true

console.log(arr instanceof Array);   // true

Important Notes About Inheritance

  • Subclasses that have a constructor must call super() before using this.
  • If a subclass does not define a constructor, it gets a default one that calls super() with all arguments.
  • The extends keyword sets up prototype-based inheritance under the hood.
  • You can only extend from a valid constructor or null. Attempting to extend from a non-constructor will throw an error.

Multiple Choice Questions 

  1. What does extends do in a class declaration?
    A. Creates a separate class with no relation.
    B. Establishes the prototype-based inheritance for the subclass from the superclass.
    C. Automatically calls super().
    D. Copies methods from one class to another.
    Answer: B
    Explanation: extends links the subclass prototype to the superclass’s prototype chain.
  2. What must a subclass constructor do before using this?
    A. Nothing special.
    B. Call this() again.
    C. Call super() to initialize the superclass part.
    D. Assign this manually.
    Answer: C
    Explanation: The subclass constructor must call super() before using this.
  3. If a subclass does not define a constructor, what happens?
    A. You cannot instantiate the subclass.
    B. It inherits the parent class’s constructor automatically.
    C. A default constructor is provided that calls super(…args).
    D. It throws a syntax error.
    Answer: C
    Explanation: A default constructor is generated that calls the parent constructor.
  4. super() in a subclass constructor: A. Must always be called first if a constructor is defined.
    B. Can be omitted if this is never used.
    C. Calls a global function named super.
    D. Is optional even if using this.
    Answer: A
    Explanation: super() must be called before accessing this in a subclass constructor.
  5. Which of the following allows calling a parent class method from a child class method?
    A. super.parentMethod()
    B. super.methodName()
    C. this.super.methodName()
    D. parent.methodName()
    Answer: B
    Explanation: super.methodName() calls the parent’s method.
  6. What happens if you try to extends from a non-constructor value?
    A. It silently fails.
    B. Throws a TypeError at runtime.
    C. Creates a normal object.
    D. It compiles but never runs.
    Answer: B
    Explanation: Extending from a non-constructor results in a TypeError.
  7. In ES6 classes, how is inheritance implemented under the hood?
    A. By copying methods from parent to child.
    B. By using prototype chains.
    C. By compiling to Java bytecode.
    D. By storing all code in a global object.
    Answer: B
    Explanation: Inheritance uses JavaScript’s prototype mechanism under the hood.
  8. super() inside a class method (not constructor) refers to:
    A. The parent class’s method with the same name.
    B. The global object.
    C. Always Object.prototype.
    D. The child’s own method.
    Answer: A
    Explanation: In class methods, super.methodName() calls the parent’s corresponding method.

What is a correct way to define a subclass?

class Vehicle {}

// ????

  1. A. class Car inherits Vehicle {}
    B. class Car extends Vehicle {}
    C. class Car super Vehicle {}
    D. class Car.parent = Vehicle {}
    Answer: B
    Explanation: The extends keyword is the correct syntax.
  2. If class Cat extends Animal {} and you do const c = new Cat(), what is the prototype chain of c?
    A. c -> Cat.prototype -> Object.prototype
    B. c -> Animal.prototype -> Cat.prototype -> Object.prototype
    C. c -> Cat.prototype -> Animal.prototype -> Object.prototype
    D. c -> Object.prototype
    Answer: C
    Explanation: The instance c inherits from Cat.prototype, which inherits from Animal.prototype, which inherits from Object.prototype.
  3. Can you extend built-in objects like Array?
    A. Yes, class MyArray extends Array {} is possible.
    B. No, you must not extend built-ins.
    C. Only if you use a special library.
    D. Only in older versions of JavaScript.
    Answer: A
    Explanation: You can extend built-ins like Array.
  4. If a parent class has a method doWork() and the child class does not define it, what happens when you call it on an instance of the child?
    A. It calls the parent’s doWork() method.
    B. Error: method not found.
    C. It returns undefined.
    D. Throws a runtime error.
    Answer: A
    Explanation: Methods not overridden in the child are inherited from the parent.
  5. Which statement about super() in subclasses is true?
    A. super() must be called after this.
    B. super() can only be called once.
    C. super() must be called before using this in the constructor.
    D. super() is optional in a subclass constructor that uses this.
    Answer: C
    Explanation: super() must be called before this in a subclass constructor.
  6. If you do not define a constructor in the subclass, how are arguments passed to the parent constructor?
    A. They aren’t; parent constructor is never called.
    B. The subclass can’t be instantiated.
    C. The parent constructor is called with no arguments.
    D. The arguments passed to the subclass’s new call are passed automatically to super().
    Answer: D
    Explanation: The default constructor is equivalent to constructor(…args) { super(…args); }.
  7. What does super.prop inside a subclass method refer to?
    A. A property on the subclass instance.
    B. A static property of the parent class.
    C. A property on the parent class’s prototype.
    D. Global variable prop.
    Answer: C
    Explanation: super.prop accesses the parent’s prototype property prop.
  8. Can you extend a class that is itself extended from another class?
    A. Yes, you can chain extends multiple times.
    B. No, only one level of inheritance is allowed.
    C. You must use super more than once.
    D. You must finalize the class first.
    Answer: A
    Explanation: Classes can form chains of inheritance.
  9. If class B extends A {} and class C extends B {}, what is Object.getPrototypeOf(C.prototype)?
    A. A.prototype
    B. B.prototype
    C. Object.prototype
    D. C itself
    Answer: B
    Explanation: C.prototype’s prototype is B.prototype.
  10. What happens if you call super() more than once in a constructor?
    A. It calls the parent constructor multiple times.
    B. It throws a runtime error.
    C. It silently ignores extra calls.
    D. It creates multiple inheritance.
    Answer: B
    Explanation: Calling super() more than once in a subclass constructor throws an error.
  11. super() must be called in a subclass constructor if:
    A. The subclass defines a constructor and uses this.
    B. The subclass never uses this.
    C. Always, even if no this is used.
    D. Never.
    Answer: A
    Explanation: If you define a constructor in a subclass and use this, you must call super() first.
  12. If you want to extend a class but provide no additional methods, can you leave the subclass body empty?
    A. Yes, class Child extends Parent {} is valid.
    B. No, you must at least have a constructor.
    C. No, must have at least one method.
    D. Only if using old syntax.
    Answer: A
    Explanation: A class body can be empty and still inherits from parent.

10 Coding Exercises with Full Solutions and Explanations

Exercise 1:
Task: Create a Person class with a constructor that takes name and a greet() method. Create a Student class that extends Person, adds a grade property, and override greet() to include the grade in the message.

Solution:

class Person {

  constructor(name) {

    this.name = name;

  }

  greet() {

    console.log(“Hello, my name is ” + this.name);

  }

}

class Student extends Person {

  constructor(name, grade) {

    super(name);

    this.grade = grade;

  }

  greet() {

    super.greet();

    console.log(“I am in grade ” + this.grade);

  }

}

const s = new Student(“Alice”, 10);

s.greet();

// “Hello, my name is Alice”

// “I am in grade 10”

Explanation: Student calls super(name) to initialize Person’s part, and overrides greet() but also calls super.greet().

Exercise 2:
Task: Create a Animal class with a sound() method that logs a generic sound. Create a Dog class that extends Animal and overrides sound() to bark. Call sound() on a Dog instance.

Solution:

class Animal {

  sound() {

    console.log(“Some generic animal sound.”);

  }

}

class Dog extends Animal {

  sound() {

    console.log(“Woof!”);

  }

}

const dog = new Dog();

dog.sound(); // “Woof!”

Explanation: Dog overrides the sound() method from Animal.

Exercise 3:
Task: Create a Vehicle class with a constructor that sets model. Create a Car class that extends Vehicle and adds a drive() method. Instantiate a Car and call drive().

Solution:

class Vehicle {

  constructor(model) {

    this.model = model;

  }

}

class Car extends Vehicle {

  drive() {

    console.log(this.model + ” is driving.”);

  }

}

const c = new Car(“Toyota”);

c.drive(); // “Toyota is driving.”

Explanation: Even though Car has no constructor, it gets a default one calling super(model).

Exercise 4:
Task: Create a Shape class with a getArea() method that returns 0. Create a Rectangle class that extends Shape, has width and height, and overrides getArea() to return width * height.

Solution:

class Shape {

  getArea() {

    return 0;

  }

}

class Rectangle extends Shape {

  constructor(width, height) {

    super();

    this.width = width;

    this.height = height;

  }

  getArea() {

    return this.width * this.height;

  }

}

const rect = new Rectangle(5, 10);

console.log(rect.getArea()); // 50

Explanation: The subclass provides a meaningful getArea().

Exercise 5:
Task: Create a Logger class with a log(message) method. Create a FileLogger that extends Logger, override log() to prefix “File:” before the message.

Solution:

class Logger {

  log(message) {

    console.log(“Log:”, message);

  }

}

class FileLogger extends Logger {

  log(message) {

    console.log(“File:”, message);

  }

}

const fl = new FileLogger();

fl.log(“Hello”);

// “File: Hello”

Explanation: FileLogger changes the behavior of log().

Exercise 6:
Task: Create a User class with username. Create Admin class extending User that adds role = ‘admin’. Print username and role from an Admin instance.

Solution:

class User {

  constructor(username) {

    this.username = username;

  }

}

class Admin extends User {

  constructor(username) {

    super(username);

    this.role = ‘admin’;

  }

}

const admin = new Admin(“adminUser”);

console.log(admin.username); // “adminUser”

console.log(admin.role);     // “admin”

Explanation: Admin sets an additional property after calling super().

Exercise 7:
Task: Subclass the built-in Error class to create a custom ValidationError. Test by throwing a new ValidationError(“Invalid input”) and catch it.

Solution:

class ValidationError extends Error {

  constructor(message) {

    super(message);

    this.name = “ValidationError”;

  }

}

try {

  throw new ValidationError(“Invalid input”);

} catch (e) {

  console.log(e.name);    // “ValidationError”

  console.log(e.message); // “Invalid input”

}

Explanation: Subclassing Error allows creating custom error types.

Exercise 8:
Task: Create a Product class with a getInfo() method. Create a Book class extending Product with title and author. Override getInfo() to print both title and author.

Solution:

class Product {

  getInfo() {

    return “Generic product”;

  }

}

class Book extends Product {

  constructor(title, author) {

    super();

    this.title = title;

    this.author = author;

  }

  getInfo() {

    return `Book: “${this.title}” by ${this.author}`;

  }

}

const b = new Book(“1984”, “George Orwell”);

console.log(b.getInfo()); // “Book: “1984” by George Orwell”

Explanation: Book overrides getInfo() to provide more specific info.

Exercise 9:
Task: Create a BaseArray that extends Array and add a last() method that returns the last element. Test it with an instance.

Solution:

class BaseArray extends Array {

  last() {

    return this[this.length – 1];

  }

}

const arr = new BaseArray(1, 2, 3, 4);

console.log(arr.last()); // 4

Explanation: Subclassing Array is possible, and we add a custom method.

Exercise 10:
Task: Create a Manager class extending User (from exercise 6) that sets role = ‘manager’. Log username and role.

Solution:

class User {

  constructor(username) {

    this.username = username;

  }

}

class Manager extends User {

  constructor(username) {

    super(username);

    this.role = ‘manager’;

  }

}

const m = new Manager(“managerUser”);

console.log(m.username); // “managerUser”

console.log(m.role);     // “manager”

Explanation: Similar to Admin, we create another subclass with a different role.

Summary

Inheritance in JavaScript, facilitated by the extends keyword and the super keyword, provides a powerful way to create subclasses that build on existing classes. By calling super() in subclass constructors, you properly initialize parent class state, and by calling super.methodName(), you can reuse parent methods while adding or modifying behavior. With inheritance, code becomes more maintainable, reusable, and logically structured, following the principles of object-oriented programming.