Menu
Coddy logo textTech

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 icon

Challenge

Easy

Let'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:
    • Phone with a Number field—its SendMessage(text string) string method returns SMS to [Number]: [text]
    • Computer with an Email field—its SendMessage(text string) string method returns Email to [Email]: [text]
    • Pigeon with a Name field—its SendMessage(text string) string method returns [Name] carries: [text]
    Notice that none of these types mention any interface—they just happen to have the same method signature.
  • sender.go: Define a Messenger interface that requires a SendMessage(text string) string method. Create a function called Broadcast that takes a slice of Messenger and a message string, then returns a slice of strings containing the result of calling SendMessage on each messenger.
  • main.go: Read the details from input, create one of each messenger type, collect them into a slice of Messenger, and use Broadcast to 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 World

The 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
}
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