Interfaces and Polymorphism
By Syncfusion®, Inc. · Last updated
Go interfaces: implicit implementation, single and multiple-method interfaces, interface values, nil interfaces, any, type assertions, type switches and polymorphism.
What Are Interfaces?
Interface = a list of methods. Any type that has all those methods can be used where the interface is asked for.
- An interface says what something can do.
- It does not say how it does it.
- Many different types can fit one interface.
Simple lesson: an interface is a list of abilities, and any type with those abilities fits.
A Real-World Example
Think of the school's charging point.
- The socket does not care what you plug in.
- A phone, a tablet or a torch: if it has the right plug, it works.
- The socket only asks for one ability: "fits this plug".
Now the mapping:
- The socket = a function that takes an interface.
- The plug shape = the interface's method list.
- Phone, tablet, torch = different types that have that method.
In Go, a type fits an interface automatically, just by having the methods.
There is no implements keyword. This is called implicit implementation.
What You Will Learn
In this lesson, you will learn:
- What interfaces are, why they are used, and how implicit implementation works.
- How to define single-method and multiple-method interfaces.
- What an interface value holds: a dynamic type and a dynamic value.
- The empty interface,
any. - Type assertions and type switches.
- Polymorphism: one piece of code working with many types.
Interface Fundamentals
Why Interfaces Are Used
- Write one function that works with many types.
- Swap parts easily: a real database or a fake one for testing.
- Hide details: the caller only sees the methods it needs.
- Connect packages without them knowing each other's types.
Implicit Implementation
Implement = have every method an interface lists.
Pick each shape. Does it fit Shape?
Look at two things:
- Rect and Circle have both methods, so they fit.
- Square and Line are each missing a method, so Go refuses them.
Two types, one interface
package main
import "fmt"
type Shape interface {
Area() float64
}
type Rect struct {
W, H float64
}
func (r Rect) Area() float64 {
return r.W * r.H
}
type Circle struct {
R float64
}
func (c Circle) Area() float64 {
return 3.14 * c.R * c.R
}
func main() {
var s Shape
s = Rect{4, 5}
fmt.Println(s.Area())
s = Circle{2}
fmt.Println(s.Area())
}
20
12.56
RectandCirclenever mentionShape.- They both have
Area() float64, so both fit. scan hold either one.
Simple lesson: a type fits an interface just by having all its methods.
Creating Interfaces
Interface Definitions
type Name interface {
Method1(params) results
Method2(params) results
}
- List only method signatures: name, parameters, results.
- No fields, no code.
Single-Method Interfaces
Go loves small interfaces. Their names often end in -er.
| Interface | Method | Means |
|---|---|---|
fmt.Stringer | String() string | can describe itself as text |
io.Reader | Read(p []byte) (int, error) | can give bytes |
io.Writer | Write(p []byte) (int, error) | can take bytes |
error | Error() string | can describe a problem |
fmt.Stringer: control how a value prints
package main
import "fmt"
type Student struct {
Name string
Class string
}
func (s Student) String() string {
return s.Name + " of class " + s.Class
}
func main() {
asha := Student{"Asha", "8A"}
fmt.Println(asha)
}
Asha of class 8A
StudenthasString() string, so it fitsfmt.Stringer.fmt.Printlnchecks for that interface and uses your method.
Multiple-Method Interfaces
An interface with two methods
package main
import "fmt"
type Shape interface {
Area() float64
Perimeter() float64
}
type Rect struct {
W, H float64
}
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
func describe(s Shape) {
fmt.Println("Area:", s.Area(), "Perimeter:", s.Perimeter())
}
func main() {
describe(Rect{3, 4})
}
Area: 12 Perimeter: 14
- A type must have every method to fit.
- Smaller interfaces are easier to fit. Prefer them.
Simple lesson: an interface is a list of method signatures, and small ones are best.
Interface Values
An interface variable holds two things inside:
- Dynamic type = the real type it holds right now, like
Rect. - Dynamic value = the real value, like
{3 4}.
Dynamic Values and Dynamic Types
One variable, changing inside
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct{}
type Cat struct{}
func (Dog) Speak() string { return "Woof" }
func (Cat) Speak() string { return "Meow" }
func main() {
var s Speaker
s = Dog{}
fmt.Println(s.Speak())
s = Cat{}
fmt.Println(s.Speak())
}
Woof
Meow
- The variable
shas the static typeSpeaker. - Its dynamic type changes from
DogtoCat. - Go calls the method of the dynamic type.
nil Interfaces
A new interface variable holds no type and no value. It is nil.
A nil interface
package main
import "fmt"
type Speaker interface {
Speak() string
}
func main() {
var s Speaker
fmt.Println(s == nil)
if s != nil {
fmt.Println(s.Speak())
} else {
fmt.Println("nobody to speak")
}
}
true
nobody to speak
- Calling a method on a nil interface panics. Check first.
An interface is only nil when both its type and value are empty.
var p *MyError = nil
var err error = p
fmt.Println(err == nil) // false in Go!
The interface holds the type *MyError, so it is not nil, even though the pointer is. Return a plain nil when there is no error. See Error Handling.
Simple lesson: an interface value holds a real type and a real value, and it is nil only when both are empty.
Empty Interface
any and interface{}
Empty interface = interface{}, an interface with no methods.
any = a shorter name for interface{}.
- Every type has "no methods required", so every value fits
any. fmt.Printlntakes...any. That is why it prints anything.
any holds anything
package main
import "fmt"
func main() {
var box any
box = 42
fmt.Println(box)
box = "Green Valley"
fmt.Println(box)
items := []any{7, 3.5, "pen", true}
fmt.Println(items, len(items))
}
42
Green Valley
[7 3.5 pen true] 4
anygives up type safety: Go no longer knows what is inside.- To use the value, you must get its real type back. See the next section.
- Prefer real types, or generics, when you can. See Generics.
Simple lesson: any can hold every value, but you must check its type before you use it.
Type Assertions
Type Assertion Basics
Type assertion = x.(T): take the value of type T out of an interface.
Get the int back
package main
import "fmt"
func main() {
var box any = 90
marks := box.(int)
fmt.Println(marks + 5)
}
95
A wrong assertion panics
package main
import "fmt"
func main() {
var box any = "ninety"
marks := box.(int)
fmt.Println(marks)
}
- The value is a
string, not anint. - The program stops:
panic: interface conversion: interface {} is string, not int.
Safe Assertions
Comma-ok assertion = v, ok := x.(T). No panic: ok tells you if it worked.
Assert safely
package main
import "fmt"
func main() {
var box any = "ninety"
if n, ok := box.(int); ok {
fmt.Println("number:", n)
} else {
fmt.Println("not a number, it is:", box)
}
}
not a number, it is: ninety
- This is the same comma-ok idea as with maps. See Maps.
Simple lesson: x.(T) gets the real value out, and v, ok := x.(T) does it safely.
Type Switches
Dynamic Type Handling
Type switch = a switch that picks a case by the type inside an interface.
Handle each type differently
package main
import "fmt"
func describe(v any) string {
switch x := v.(type) {
case int:
return fmt.Sprint("whole number ", x)
case float64:
return fmt.Sprint("decimal ", x)
case string:
return "text of length " + fmt.Sprint(len(x))
case nil:
return "nothing"
default:
return "something else"
}
}
func main() {
fmt.Println(describe(42))
fmt.Println(describe(3.5))
fmt.Println(describe("Asha"))
fmt.Println(describe(nil))
fmt.Println(describe(true))
}
whole number 42
decimal 3.5
text of length 4
nothing
something else
v.(type)works only inside aswitch.- In each case,
xalready has that case's type. - In
case string,len(x)works becausexis astring.
Simple lesson: a type switch runs a different case for each type inside an interface.
Polymorphism
Polymorphism = one piece of code works with many types, and each type behaves in its own way.
Interface-Based Polymorphism
In Go, polymorphism comes from interfaces, not from class families.
- A function or loop works with the interface.
- Each type gives its own version of the methods.
Multiple Implementations
Press Play to watch one loop call three different methods.
One loop, many behaviours
Runtime Behavior
- The loop is written once.
- Which
Sendruns is decided while the program runs, by the dynamic type. - Add a new type, like
WhatsApp, with aSendmethod, and the loop works with it unchanged.
Total area of mixed shapes
package main
import "fmt"
type Shape interface {
Area() float64
}
type Rect struct{ W, H float64 }
type Square struct{ Side float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (s Square) Area() float64 { return s.Side * s.Side }
func totalArea(shapes []Shape) float64 {
total := 0.0
for _, s := range shapes {
total += s.Area()
}
return total
}
func main() {
garden := []Shape{Rect{10, 4}, Square{3}, Rect{2, 2.5}}
fmt.Println("Garden area:", totalArea(garden))
}
Garden area: 54
Easy way to remember
- An interface = "what can it do?"
- A type fits by having the methods. No
implements. - Accept interfaces in functions, return concrete types.
Simple lesson: with interfaces, one function works with many types, and each type keeps its own behaviour.
TL;DR
- Interface = a list of methods:
type Shape interface { Area() float64 }. - Implicit implementation = a type fits by having every method. No
implements. - Small interfaces =
Stringer,Reader,Writer,error. - Interface value = dynamic type + dynamic value.
nilonly when both are empty. - Empty interface =
any/interface{}. Holds every value. - Type assertion =
x.(T), panics if wrong. Safe =v, ok := x.(T). - Type switch =
switch v := x.(type) { case int: ... }. - Polymorphism = one loop or function, many types, each with its own behaviour.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Writing implements Shape | Go needs it | Go matches methods automatically | A syntax error. |
| Missing one method | Close enough is fine | Every method is needed | does not implement Shape (missing method Perimeter) |
| Pointer-receiver methods with a value | A value fits | Only *T has pointer methods | method Area has pointer receiver |
x.(int) without checking | It always works | Wrong types panic | interface conversion panic. |
Using any everywhere | It is flexible | It loses type checks | Bugs found only at runtime. |
Returning a nil pointer as error | It equals nil | The interface is not nil | err != nil is true by mistake. |
| Huge interfaces | More methods = better | Small ones are easier to fit | Few types can use them. |
Check Yourself
Exercises
What to Remember
- An interface lists methods. Any type with those methods fits, automatically.
- Keep interfaces small:
Stringer,Reader,error. - An interface value holds a type and a value. It is nil only when both are empty.
anyholds everything. Get values back withx.(T),v, ok := x.(T)or a type switch.- Polymorphism: one loop, many types, each behaving its own way.
Next: Encapsulation. You will learn how to hide a type's data and control how it changes.