Start learning free

LESSON 185 OF 344 · OBJECT-ORIENTED PROGRAMMING

Abstract Classes in JavaScript: The Throwing Method Pattern

Lesson Read the full explanation

TL ; DR:

An abstract class is a class you can't instantiate directly — it's a half-finished template. It can declare methods with no body (abstract methods) that any concrete subclass must implement.


class Animal {
    speak() { throw new Error("Not implemented"); }
}
class Dog extends Animal {
    speak() { console.log("Woof"); }
}


Full lesson:

Sometimes you want to capture shared behavior across a family of classes without committing to one concrete implementation. For example, every Animal can speak(), but exactly how depends on whether it's a Dog or a Cat. That's what abstract classes are for.

Two superpowers

  1. You can't write new Animal() directly — the compiler/runtime stops you. You have to pick a concrete subclass.
  2. Any subclass MUST implement every abstract method, or the compiler/runtime complains. The abstract class is a contract with teeth.

Mixing abstract and concrete

An abstract class can also have concrete methods (with bodies) and fields. Those are shared by all subclasses for free, just like normal inheritance. Only the methods marked abstract require an override.

Notes by language

  • C# / Java — keyword abstract on the class and on the method.
  • Python — inherit from abc.ABC and decorate the method with @abstractmethod.
  • JavaScript — no built-in support. The convention is a parent class whose method throws an error if not overridden.
  • C++ — a method ending with = 0 is "pure virtual"; a class with any pure virtual method is abstract.

Your task

The abstract Animal class is already defined. Create a concrete Dog subclass and implement the speak method so it prints "Woof". The harness builds a Dog and calls speak().

Expected output:

Woof
STEP 1 OF 2
    Ctrl + Enter to check

    JAVASCRIPT · OBJECT-ORIENTED PROGRAMMING

    Abstract Classes in JavaScript, explained

    What an abstract class is

    An abstract class is a half-finished template. It captures behavior shared by a family of classes without committing to one concrete version. Every Animal can speak(), for example, but how it speaks depends on whether it is a dog or a cat. The abstract class declares that every animal speaks, and the concrete subclasses supply the sound.

    Languages with full support give an abstract class two properties:

    1. You can't create an instance of it directly. Writing new Animal() is rejected, and you must pick a concrete subclass.
    2. Every subclass must implement each abstract method, or the compiler refuses the program. The abstract class is a contract that is enforced.

    An abstract class can also have ordinary methods with bodies. Subclasses inherit those like any other inherited method, and only the abstract methods have to be overridden.

    JavaScript has no abstract keyword

    C# and Java mark classes and methods with abstract. Python uses abc.ABC, and C++ uses pure virtual methods. JavaScript has nothing built in. The usual convention is a parent class whose method throws an error, so a subclass that forgets to override it fails as soon as the method is called:

    class Animal {
        speak() { throw new Error("Not implemented"); }
    }
    
    class Dog extends Animal {
        speak() { console.log("Woof"); }
    }
    

    The error is a way to say "a subclass must override this". It is a runtime check, which means you find the mistake when the code runs, not when you write it. Both of these are valid JavaScript:

    class Fish extends Animal {}
    
    new Dog().speak();    // Woof
    new Fish().speak();   // Error: Not implemented
    

    Fish forgot to implement speak, and the program only fails at the call. Note also that new Animal() still works, because nothing prevents it; only calling its speak() throws.

    Blocking direct instantiation

    If you also want new Animal() to fail, check new.target in the constructor. Inside a constructor, new.target is the class that new was applied to:

    class Shape {
        constructor() {
            if (new.target === Shape) {
                throw new TypeError("Shape is abstract");
            }
        }
        area() {
            throw new Error(this.constructor.name + " must implement area()");
        }
        describe() {
            return this.constructor.name + " with area " + this.area();
        }
    }
    
    class Square extends Shape {
        constructor(side) {
            super();
            this.side = side;
        }
        area() { return this.side * this.side; }
    }
    
    console.log(new Square(3).describe());   // Square with area 9
    new Shape();                             // TypeError: Shape is abstract
    

    Here describe() is a concrete method shared by every subclass, and it calls area(), which each subclass must provide. A subclass that lacks area() fails with the message "Blob must implement area()" when describe() runs, naming the offending class through this.constructor.name.

    The constructor can also catch a missing override earlier, at creation time instead of at the call. Comparing this.area with Shape.prototype.area shows whether the subclass replaced the method, and the constructor can throw if it didn't. That makes a forgotten method fail the moment the object is built.

    The exercise, step by step

    The starter code defines Animal with a throwing speak, and ends with let rex = new Dog(); rex.speak();. Dog doesn't exist yet.

    1. Create the concrete subclass. Below Animal, write class Dog extends Animal { }.
    2. Implement the method. Inside Dog, write speak() and make it log "Woof". This replaces the parent's version, so the throwing code never runs for dogs. Without the override, rex.speak() would end the program with the "Not implemented" error.

    The solution in JavaScript

    class Animal {
        speak() { throw new Error("Not implemented"); }
    }
    
    class Dog extends Animal {
        speak() {
            console.log("Woof");
        }
    }
    
    let rex = new Dog();
    rex.speak();
    

    The output is:

    Woof
    

    Common mistakes

    • Expecting the language to complain early. Nothing is checked when the class is defined. A missing override shows up only when the method is called, so test each subclass.
    • Misspelling the method in the subclass. Speak() or speek() is a new method, and calling speak() still throws the parent's error.
    • Forgetting extends. A Dog that doesn't extend Animal has its own speak, but it isn't an Animal: rex instanceof Animal is false.
    • Using new.target without super(). A subclass constructor that has to run the parent's check must call super() before using this.
    • Swallowing the error. Wrapping calls in a try/catch that ignores errors hides the very message that tells you a method is missing.

    Key takeaways

    • An abstract class is a template with methods that subclasses must supply.
    • JavaScript has no abstract keyword; by convention the parent method throws an error.
    • A subclass makes the method concrete by overriding it.
    • Checking new.target in the constructor can also prevent creating the abstract class itself.
    Keep going with JavaScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free