Start learning free

LESSON 189 OF 338 · OBJECT-ORIENTED PROGRAMMING

Abstract Classes in Go: Interfaces as Contracts

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.


// Animal is an interface — you cannot use it directly as a value
type Animal interface {
    speak()
}

type Dog struct{}

func (d Dog) speak() { fmt.Println("Woof") }


Full lesson:

Sometimes you want to capture shared behavior across a family of types 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 interfaces are for.

Two superpowers

  1. You can't use Animal as a concrete type directly — you have to provide a concrete struct that satisfies the interface.
  2. Any struct assigned to an Animal variable MUST implement every method of the interface, or the compiler complains. The interface is a contract with teeth.

Mixing abstract and concrete

In Go, interfaces declare only method signatures — there are no concrete fields or default method bodies inside an interface. Shared behavior lives in the structs that implement the interface. Only the methods declared in the interface require an implementation.

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 Animal interface is already defined. Create a concrete Dog struct and implement the speak method so it prints "Woof". The harness builds a Dog and calls speak().

Expected output:

Woof
STEP 1 OF 3
    Ctrl + Enter to check

    GO · OBJECT-ORIENTED PROGRAMMING

    Abstract Classes in Go, explained

    The idea of an abstract class

    In some languages an abstract class is a half-finished template: you cannot create one directly, and it may declare methods without bodies that every concrete subclass must implement. The goal is a contract: all the types in a family promise to offer the same operations, each in its own way.

    Go has no abstract classes. The contract is expressed by an interface, a set of method signatures with no bodies and no data:

    type Animal interface {
        speak()
    }
    

    What the interface guarantees

    • You cannot make an Animal by itself. Writing the literal Animal{} is a compile error, because an interface has no fields to fill in. You can only store some concrete value in an Animal variable. A declared var a Animal without a value holds nil, and calling a method on it panics.
    • The implementation must be complete. Assigning a value to an Animal variable compiles only if its type has every method of the interface. Otherwise the compiler reports the missing method.

    For example, with var a Animal = Dog{} and a Dog that has no methods, the compiler reports that Dog does not implement Animal (missing method speak).

    Implementing it

    A concrete type implements the interface just by having the methods. There is no implements or extends keyword:

    type Dog struct {}
    
    func (this Dog) speak() {
        fmt.Println("Woof")
    }
    

    The receiver in parentheses attaches speak to Dog. From that moment Dog satisfies Animal, even though the code never mentions the interface. Any other type with a matching speak() would satisfy it too.

    Shared behaviour: no default method bodies

    An abstract class in other languages can mix abstract methods with concrete ones that subclasses inherit. A Go interface cannot hold any code, so shared behaviour lives somewhere else. The usual place is a small struct with the common fields and methods, which implementing types embed:

    type base struct{ name string }
    
    func (b base) describe() string { return "I am " + b.name }
    
    type Dog struct{ base }
    
    func (d Dog) speak() { fmt.Println("Woof") }
    

    Here Dog gets describe from the embedded struct and defines speak itself, so the interface carries the contract and embedding carries the shared code. Interfaces in Go are also usually small, often one or two methods, and are declared by the code that uses them.

    Why not just use the struct?

    The contract pays off in functions. A function that takes an Animal accepts every type that satisfies it, and it can be tested with a simple fake type that has only speak. A function that took a Dog would accept nothing else. The interface says what the function needs and nothing more, which keeps the types decoupled.

    The lesson's exercise

    The Animal interface and a mainFunction that stores a Dog{} in an Animal variable are already there. You supply the concrete type.

    1. Declare an empty Dog struct below the interface. The interface cannot be instantiated, so a concrete type must stand behind it.
    2. Open a speak method with a Dog receiver named this. This is what makes Dog satisfy Animal.
    3. Print Woof with fmt.Println.
    type Animal interface {
        speak()
    }
    
    type Dog struct {}
    
    func (this Dog) speak() {
        fmt.Println("Woof")
    }
    
    
    
    func mainFunction() {
        var rex Animal = Dog{}
        rex.speak()
    }
    

    It prints:

    Woof
    

    Common mistakes

    • Using a different method name. Speak and speak are different names in Go, so a Dog with Speak() does not satisfy an interface that asks for speak().
    • Using a different signature. The parameters and results must match the interface exactly.
    • A pointer receiver with a value. If speak were declared on *Dog, then var rex Animal = Dog{} would not compile. Use &Dog{} or a value receiver.
    • Putting fields or bodies in the interface. Neither is allowed. Interfaces hold only method signatures.

    Key takeaways

    • Go uses interfaces where other languages use abstract classes.
    • An interface cannot be instantiated, and implementing types must supply every method.
    • Implementation is implicit: having the methods is enough.
    • Shared code goes in embedded structs, not in the interface.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free