Start learning free

LESSON 186 OF 344 · OBJECT-ORIENTED PROGRAMMING

Interfaces in JavaScript: Contracts Without an Interface Keyword

Lesson Read the full explanation

TL ; DR:

An interface is a pure contract — a list of methods that a class promises to implement, with no shared data and no shared code. Unlike inheritance ("is a"), interfaces express "can do".


// JS has no `interface` keyword; convention is a stub base class:
class Speaker {
    speak() { throw new Error("Not implemented"); }
}
class Dog extends Speaker {
    speak() { console.log("Woof"); }
}


Full lesson:

An interface defines what a class can do, not how it does it or what it is. A Truck, a Robot, and a Drone aren't the same kind of thing — but they can all Move(). You'd put Move() in a Movable interface and have each one implement it.

Interface vs abstract class

Abstract classInterface
Has fieldsYesUsually no
Has method bodiesYes (some)No (or default impls only)
Multiple "inheritance"Usually noYes — a class can implement many interfaces
Models"is a""can do"

A class inherits from at most one parent class but can implement many interfaces. That's why interfaces are the right tool for cross-cutting capabilities like Comparable, Serializable, Iterable, etc.

The language landscape

  • C# / Java — first-class interface keyword. C# uses : for both inheritance and implementation; Java uses extends for inheritance and implements for interfaces.
  • Python — no separate interface; abstract base classes (ABCs) with all-abstract methods serve the same role.
  • JavaScript — no interface keyword in plain JS (TypeScript has it). The convention is a stub class.
  • C++ — a class with only pure-virtual methods is an interface. Multiple inheritance lets a class implement several.

Your task

The Speaker interface (or interface-like base) is already declared. Create a Dog class that implements it, with a speak() method that prints "Woof".

Expected output:

Woof
STEP 1 OF 2
    Ctrl + Enter to check

    JAVASCRIPT · OBJECT-ORIENTED PROGRAMMING

    Interfaces in JavaScript, explained

    What an interface is

    An interface is a contract: a list of methods that a class promises to provide. It says what an object can do, not how it does it and not what the object is. A Truck, a Robot and a Drone are different kinds of things, but all three can move(). The code that moves things only needs to know about that one method.

    That is the difference from inheritance. Inheritance models "is a": a Dog is an Animal, and it receives the parent's fields and method bodies. An interface models "can do": it carries no data and no shared code, only the promise. A class has one parent at most, but it can make as many promises as it likes, so interfaces suit abilities that cut across the class tree, such as being comparable, serialisable or iterable.

    JavaScript has no interface keyword

    Languages like Java and C# have an interface keyword, and the compiler refuses a class that leaves a promised method out. Plain JavaScript has no such keyword (TypeScript adds one, but it disappears when the code is compiled to JavaScript), and nothing checks a class against a contract before the program runs. JavaScript programmers therefore use conventions. The simplest is a stub base class whose methods throw:

    class Speaker {
        speak() { throw new Error("Not implemented"); }
    }
    

    A class that wants to be a Speaker extends this class and overrides speak(). If it forgets, calling speak() reaches the stub and throws, so the missing method shows up as soon as it is used instead of as a silent wrong answer.

    Why the stub throws

    An empty body would be worse. A subclass that forgot to override speak() would run the empty method, do nothing, and look like it worked. Throwing makes the broken promise loud. Notice that the check happens at the call, not when the class is declared:

    class Speaker {
        speak() { throw new Error("Not implemented"); }
    }
    
    class Fish extends Speaker {}
    
    const nemo = new Fish();       // no error here
    try {
        nemo.speak();
    } catch (e) {
        console.log(e.message);    // Not implemented
    }
    

    Declaring Fish and creating a Fish both succeed. Only the call fails.

    The exercise: a Dog that can speak

    The exercise starts with Speaker already written, followed by two lines that create a Dog called rex and call rex.speak(). The expected output is Woof. The work is in two steps.

    1. Declare the class. Write class Dog extends Speaker between Speaker and the lines that use it. The class declaration has to come before new Dog() runs. Unlike functions, classes can't be used before their declaration is reached.
    2. Override the method. Inside Dog, define speak() and make it log Woof with console.log. This version replaces the one inherited from Speaker, so the stub that throws is never reached for a Dog.

    The finished program:

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

    It prints:

    Woof
    

    Gotchas in JavaScript

    • Nothing enforces the contract early. A class that forgets a method is still accepted. The error appears only when that method is called, which might be in a rarely used branch of the program.
    • The stub can be instantiated. new Speaker() works and gives an object whose speak() throws. If you want to forbid that, check new.target in the constructor and throw when it equals the base class.
    • A checker based on the prototype is fooled by the stub. Because Fish inherits speak from Speaker, typeof Fish.prototype.speak === "function" is true even though Fish never wrote its own version. To tell, compare against the stub: Fish.prototype.speak === Speaker.prototype.speak.
    • Spelling matters. Method names are plain strings. A subclass that defines Speak() or speek() overrides nothing, and the stub still throws.
    • A class extends only one class. The stub-class convention gives you a single contract per class. Capabilities can still be combined, as the next section shows.

    Other ways to express a contract

    Duck typing. Often no base class is needed at all. Any object with a speak method can be used where a speaker is expected, whatever its class. The code that consumes it just calls the method, or checks first:

    const canSpeak = (o) => typeof o.speak === "function";
    
    console.log(canSpeak({ speak() {} })); // true
    console.log(canSpeak({}));             // false
    

    Several contracts on one class. A function that takes a base class and returns a subclass of it, often called a mixin, lets a class pick up several capabilities even though extends accepts only one parent:

    const Swimmer = (Base) => class extends Base {
        swim() { return "swim"; }
    };
    const Walker = (Base) => class extends Base {
        walk() { return "walk"; }
    };
    
    class Animal {}
    class Duck extends Walker(Swimmer(Animal)) {}
    
    const d = new Duck();
    console.log(d.swim(), d.walk()); // swim walk
    

    Unlike a true interface, a mixin like this carries its code with it. The point is only that one class can end up with more than one capability.

    Interface versus abstract class

    An abstract class may hold fields and finished methods next to the stubs, and a subclass shares them. An interface holds none of that, only method names. In JavaScript the stub class blurs the two, because the same class syntax serves both. The habit that keeps them apart is a matter of what you put inside. If the base class has only throwing stubs, it plays the interface role. If it also stores data or has working methods that subclasses reuse, it is an abstract class.

    Key takeaways

    • An interface lists what a class can do, with no data and no shared code. Inheritance says what a class is.
    • JavaScript has no interface keyword. The usual convention is a base class whose methods throw Error("Not implemented").
    • A class implements the contract by extending the base class and overriding every method.
    • A missing override is found when the method is called, not when the class is declared.
    • Duck typing and mixins cover cases where one base class is not enough.
    Keep going with JavaScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free