Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
240: Type Switches on Interfaces
A few years ago, I was reviewing a PR from a junior dev who was building a message dispatcher for a distributed system. He had this massive block of code—nearly a hundred lines—consisting of a repetitive chain of if v, ok := msg.(UserCreated); ok { ... } else if v, ok := msg.(OrderPlaced); ok { ... }. It was a "type assertion ladder," and it was a nightmare to read. Every time he added a new event type, the ladder grew, and the indentation started drifting toward the right side of the screen. I told him, "You're doing the right thing logically, but you're fighting the language." In Go, when you find yourself asserting the same interface over and over, you should be using a type switch.
Moving Beyond Manual Type Assertions
You already know that a type assertion allows you to extract the concrete value from an interface. But when a single interface variable could realistically be one of five or ten different types, those if-ok blocks become noise. A type switch is essentially syntactic sugar that tells the Go compiler: "I know this is an interface, but I want to perform different logic based on what the concrete type actually is."
The magic happens with the .(type) expression. Note that this syntax is only valid inside a switch statement. If you try to use .(type) in an if block, the compiler will yell at you. It’s a specialized construct designed specifically for this pattern.
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
type Square struct {
Side float64
}
func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius }
func (s Square) Area() float64 { return s.Side * s.Side }
func DescribeShape(s Shape) {
// Here is the type switch
switch v := s.(type) {
case Circle:
fmt.Printf("This is a circle with radius %.2f\n", v.Radius)
case Square:
fmt.Printf("This is a square with side %.2f\n", v.Side)
default:
fmt.Println("Unknown shape type")
}
}
Capturing the Concrete Value
Look closely at the line switch v := s.(type). This is the most critical part of the pattern. By assigning the result to v, Go automatically casts v to the correct concrete type within each case block. Inside the case Circle block, v isn't a Shape anymore—it's a Circle. You get full access to the Radius field without having to perform another manual assertion.
I often see developers forget the assignment and just write switch s.(type). While that works if you only care about the type and not the value, you'll almost always need the value to actually do anything useful. Always use the v := ... form unless you have a very specific reason not to.
Handling Multiple Types in One Case
Sometimes, you don't need different logic for every single type; you just need to know if the value belongs to a specific group of types. You can comma-separate types in a single case. However, there is a catch: if you group types, you cannot use the assigned variable v as a concrete type because the compiler doesn't know which one of the group v actually is. In those cases, v remains the original interface type.
switch v := s.(type) {
case Circle, Square:
// v is still of type Shape here, not Circle or Square
fmt.Printf("This is a geometric shape with area %.2f\n", v.Area())
case string:
fmt.Println("Someone passed a string instead of a Shape!")
default:
fmt.Println("Totally unsupported type")
}
I personally find that using the default case is non-negotiable here. Since interfaces are open—meaning anyone can implement them—you can never be 100% sure that a new type won't be passed into your function in the future. The default case is your safety net against panics or silent failures.
📋 Practical Task
Exercise: Implementing a Multi-Format Event Processor
You are building a telemetry system that receives data from various sources. Some sources send data as simple strings, some as integers (representing status codes), and some as a structured TelemetryEvent struct. Your goal is to process these diverse types using a single handler function.
Requirements:
- Define a struct called
TelemetryEventwith two fields:Source stringandValue float64. - Write a function called
ProcessData(data any)(using theanyalias forinterface{}). - Inside
ProcessData, use a type switch to handle the following:- If the data is a
string, print:"Log Message: [string value]". - If the data is an
int, print:"Status Code: [int value]". - If the data is a
TelemetryEvent, print:"Event from [Source]: [Value]". - For any other type, print:
"Unknown data format received".
- If the data is a
- In your
mainfunction, create a slice ofanycontaining one of each of the above types, plus one unsupported type (like abool), and loop through them callingProcessDatafor each.
There are no comments for now.