Card 06/ 09

ConceptDifficulty: Intermediate3 min

Interfaces in Python, Java and TypeScript

An ABC, a Protocol, a Java interface and a TypeScript interface all say "anything with these methods".

An interface is a type that lists the methods an object must have, without saying how they work. Most SOLID fixes introduce one, so you need to read it in the language in front of you. This card shows how Python, Java and TypeScript each declare an interface, and the one difference that matters: whether a class must name the interface it fulfils, which is called nominal typing, or merely have the right methods, which is called structural typing.

Each listing below declares a Sender with one send method, writes one class that fulfils it, and calls it through the interface. The calling code never mentions the concrete class's name, and that is the whole point.

One Sender interface in three languages

An abstract base class (ABC) from the abc module. A subclass must inherit from it and implement every abstract method, or Python refuses to create the object.

py
from abc import ABC, abstractmethod


class Sender(ABC):
    @abstractmethod
    def send(self, to: str, body: str) -> None: ...


class EmailSender(Sender):
    def send(self, to: str, body: str) -> None:
        print(f"Email to {to}: {body}")


class Unfinished(Sender):
    pass


EmailSender().send("[email protected]", "Order shipped")
try:
    Unfinished()
except TypeError as error:
    print(error)
OutputRan with python:3.12-slim (Python 3.12.13)
Email to [email protected]: Order shipped
Can't instantiate abstract class Unfinished without an implementation for abstract method 'send'

A Protocol from the typing module. SmsSender never mentions Sender; a type checker accepts it because it has the right method. Python itself does not check this at run time.

py
from typing import Protocol


class Sender(Protocol):
    def send(self, to: str, body: str) -> None: ...


class SmsSender:
    def send(self, to: str, body: str) -> None:
        print(f"SMS to {to}: {body}")


def tell_customer(sender: Sender) -> None:
    sender.send("07700 900123", "Order shipped")


tell_customer(SmsSender())
OutputRan with python:3.12-slim (Python 3.12.13)
SMS to 07700 900123: Order shipped

A Java interface. The class must say implements Sender, and the compiler checks every method.

java
public class IfaceJava {
    static void tellCustomer(Sender sender) {
        sender.send("[email protected]", "Order shipped");
    }

    public static void main(String[] args) {
        tellCustomer(new EmailSender());
    }
}

interface Sender {
    void send(String to, String body);
}

class EmailSender implements Sender {
    public void send(String to, String body) {
        System.out.println("Email to " + to + ": " + body);
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
Email to [email protected]: Order shipped

A TypeScript interface. PushSender never names it; the compiler accepts it because the shape matches.

ts
interface Sender {
  send(to: string, body: string): void;
}

class PushSender {
  send(to: string, body: string): void {
    console.log(`Push to ${to}: ${body}`);
  }
}

function tellCustomer(sender: Sender): void {
  sender.send("device-42", "Order shipped");
}

tellCustomer(new PushSender());
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
Push to device-42: Order shipped

Remove implements Sender from a Java class and call it the same way, and the build stops with SmsSender cannot be converted to Sender. Java only trusts what a class declares. TypeScript and a Python Protocol trust what a class has.

How each language decides that a class fulfils an interface
DeclarationClass must name itChecked when
Python ABCYes, by inheritingAt run time, when the object is created
Python ProtocolNo, structuralOnly by a type checker such as mypy
Java interfaceYes, with implementsAt compile time
TypeScript interfaceNo, structuralAt compile time

For SOLID the difference is mostly about convenience. A structural interface lets you fit an existing class, even one from a library, without editing it. A nominal one makes the intent visible on the class itself. Either way the high-level code depends on Sender, not on any concrete sender.