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
EasyLet'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 aDescriberinterface that requires aDescribe() stringmethod. Then create three vehicle types that each implement this interface in their own way:CarwithBrandandModelfields—itsDescribe()returnsCar: [Brand] [Model]MotorcyclewithBrandandEngineCC(int) fields—itsDescribe()returnsMotorcycle: [Brand] [EngineCC]ccBicyclewithTypefield (like "Mountain" or "Road")—itsDescribe()returnsBicycle: [Type]
main.go: Create a function calledPrintDescriptionthat accepts anyDescriberand prints the result of callingDescribe(). Read vehicle details from input, create one of each vehicle type, and pass each one toPrintDescriptionto 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: MountainNotice 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")
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool