Duck Typing in Go
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 42 of 107.
The term duck typing comes from the saying: "If it walks like a duck and quacks like a duck, then it's a duck." In Go, this means a type doesn't need to explicitly declare that it implements an interface—it just needs to have the right methods.
Consider this interface and two completely unrelated types:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Neither Duck nor Robot mentions Quacker anywhere in their definitions. Yet both satisfy the interface simply because they have a Quack() method with the correct signature:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
This implicit satisfaction is powerful because it allows you to define interfaces after the types already exist. You can create an interface that matches types from external packages without modifying their source code. The types don't need to know about your interface—they just need to behave correctly.
Duck typing in Go gives you the flexibility of dynamic languages while maintaining compile-time type safety. The compiler verifies that types actually have the required methods before allowing them to be used as interface values.
Challenge
EasyLet's build a messaging system that demonstrates duck typing in action. You'll create completely unrelated types that can all send messages through a common interface—without any of them explicitly declaring they implement it.
You'll organize your code across three files:
messengers.go: Create three unrelated structs that happen to share the same behavior:Phonewith aNumberfield—itsSendMessage(text string) stringmethod returnsSMS to [Number]: [text]Computerwith anEmailfield—itsSendMessage(text string) stringmethod returnsEmail to [Email]: [text]Pigeonwith aNamefield—itsSendMessage(text string) stringmethod returns[Name] carries: [text]
sender.go: Define aMessengerinterface that requires aSendMessage(text string) stringmethod. Create a function calledBroadcastthat takes a slice ofMessengerand a message string, then returns a slice of strings containing the result of callingSendMessageon each messenger.main.go: Read the details from input, create one of each messenger type, collect them into a slice ofMessenger, and useBroadcastto send a message through all of them. Print each result on its own line.
The following inputs will be provided:
- Line 1: Phone number
- Line 2: Email address
- Line 3: Pigeon name
- Line 4: Message to broadcast
For example, given 555-1234, alice@mail.com, Winston, and Hello World, your output should be:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldThe key insight here is that Phone, Computer, and Pigeon have nothing in common—they don't share a base type or explicitly implement Messenger. Yet because they all "quack" the same way (have the matching method), Go lets them all be used as Messenger values. This is duck typing at work.
Cheat sheet
In Go, duck typing means a type doesn't need to explicitly declare that it implements an interface—it just needs to have the required methods with matching signatures.
A type implicitly satisfies an interface if it has all the methods the interface requires:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Both Duck and Robot satisfy the Quacker interface without mentioning it in their definitions:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
This implicit satisfaction allows you to define interfaces after types already exist, and even create interfaces that match types from external packages without modifying their source code. The compiler verifies at compile-time that types have the required methods, providing type safety while maintaining flexibility.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Read phone number
scanner.Scan()
phoneNumber := scanner.Text()
// Read email address
scanner.Scan()
email := scanner.Text()
// Read pigeon name
scanner.Scan()
pigeonName := scanner.Text()
// Read message to broadcast
scanner.Scan()
message := scanner.Text()
// TODO: Create one of each messenger type (Phone, Computer, Pigeon)
// TODO: Collect all messengers into a slice of Messenger
// TODO: Use Broadcast to send the message through all messengers
// TODO: Print each result on its own line
// Placeholder to use variables (remove when implementing)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
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