Menu
Coddy logo textTech

Polymorphism via Interfaces

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 41 of 107.

Polymorphism allows different types to be treated uniformly through a shared interface. In Go, this is achieved entirely through interfaces, without inheritance or class hierarchies.

When a function accepts an interface type as a parameter, any concrete type that implements that interface can be passed in. The function doesn't need to know the specific type—it only cares about the behavior defined by the interface:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

Now MakeSound works with any type that has a Speak() method:

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // Woof!
    MakeSound(cat)  // Meow!
}

The same function call produces different behavior depending on the actual type passed. This is polymorphism in action. The MakeSound function is written once but works with an unlimited number of types, as long as they satisfy the Speaker interface.

This approach keeps your code flexible and extensible. Adding a new type that speaks requires no changes to existing functions—just implement the interface, and it works automatically.

challenge icon

Challenge

Easy

Let's build a vehicle description system that demonstrates polymorphism in action. You'll create different vehicle types that all share a common behavior through an interface, then write a single function that works with any vehicle.

You'll organize your code across two files:

  • vehicles.go: Define a Describer interface that requires a Describe() string method. Then create three vehicle types that each implement this interface in their own way:
    • Car with Brand and Model fields—its Describe() returns Car: [Brand] [Model]
    • Motorcycle with Brand and EngineCC (int) fields—its Describe() returns Motorcycle: [Brand] [EngineCC]cc
    • Bicycle with Type field (like "Mountain" or "Road")—its Describe() returns Bicycle: [Type]
  • main.go: Create a function called PrintDescription that accepts any Describer and prints the result of calling Describe(). Read vehicle details from input, create one of each vehicle type, and pass each one to PrintDescription to demonstrate that the same function works with all three different types.

The following inputs will be provided:

  • Line 1: Car brand
  • Line 2: Car model
  • Line 3: Motorcycle brand
  • Line 4: Motorcycle engine CC (integer)
  • Line 5: Bicycle type

For example, given Toyota, Camry, Honda, 600, and Mountain, your output should be:

Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: Mountain

Notice how PrintDescription doesn't need to know whether it's receiving a Car, Motorcycle, or Bicycle—it simply calls Describe() and each type responds with its own unique output. This is polymorphism: one function, multiple behaviors.

Cheat sheet

Polymorphism allows different types to be treated uniformly through a shared interface. In Go, this is achieved through interfaces without inheritance or class hierarchies.

When a function accepts an interface type as a parameter, any concrete type that implements that interface can be passed in:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

The same function works with different types:

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // Woof!
    MakeSound(cat)  // Meow!
}

The function is written once but works with unlimited types, as long as they satisfy the interface. Adding new types requires no changes to existing functions—just implement the interface.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// TODO: Create a function called PrintDescription that accepts any Describer
// and prints the result of calling Describe()

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Read car details
	carBrand, _ := reader.ReadString('\n')
	carBrand = strings.TrimSpace(carBrand)
	carModel, _ := reader.ReadString('\n')
	carModel = strings.TrimSpace(carModel)

	// Read motorcycle details
	motoBrand, _ := reader.ReadString('\n')
	motoBrand = strings.TrimSpace(motoBrand)
	motoEngineStr, _ := reader.ReadString('\n')
	motoEngineStr = strings.TrimSpace(motoEngineStr)
	motoEngine, _ := strconv.Atoi(motoEngineStr)

	// Read bicycle details
	bicycleType, _ := reader.ReadString('\n')
	bicycleType = strings.TrimSpace(bicycleType)

	// TODO: Create a Car, Motorcycle, and Bicycle using the input values

	// TODO: Call PrintDescription for each vehicle to demonstrate polymorphism
	fmt.Println("TODO: Print vehicle descriptions")
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming