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
23: Empty Interface and Type Assertions
What exactly is an empty interface anyway?
You'll see interface{} everywhere in older Go code, and in newer versions, you'll see the alias any. They are the exact same thing. Think of an empty interface as a container that can hold literally any value—an integer, a string, a custom struct, or even another interface. Why? Because an interface defines a set of methods a type must have. Since the empty interface has zero methods, every single type in Go satisfies it by default.
I usually tell people to think of it as the "escape hatch" of Go's type system. I've used this a lot when writing functions that need to handle data where the type isn't known until runtime, like parsing a JSON response from an API where a field could be a number or a string.
package main
import "fmt"
func describe(i any) {
fmt.Printf("Value: %v, Type: %T\n", i, i)
}
func main() {
describe(42) // Works!
describe("Hello Go") // Works!
describe(true) // Works!
}
How do I get the concrete type back out?
Holding a value in an any container is easy, but it's also a bit useless because you can't do anything type-specific with it. You can't add two any variables together, even if you know they are both integers. To get the original value back, you use a type assertion.
The syntax is value.(Type). It's basically you telling the compiler, "Trust me, I know this empty interface is actually a string."
func printLength(i any) {
// We assert that 'i' is a string so we can use len()
s := i.(string)
fmt.Println("Length is:", len(s))
}
What happens if I guess the type wrong?
If you use the single-value assertion I showed above and you're wrong—say, you pass an integer into printLength—your program will panic and crash immediately. In a production environment, that's a nightmare. I almost never use the single-value assertion unless I'm 100% certain of the type (which is rarer than you'd think).
Instead, use the "comma-ok" idiom. This gives you a second boolean value that tells you if the assertion succeeded without blowing up your app.
func safePrintLength(i any) {
s, ok := i.(string)
if !ok {
fmt.Println("Oops, this wasn't a string!")
return
}
fmt.Println("Length is:", len(s))
}
Can I handle multiple different types in one block?
If you find yourself writing a long chain of if/else statements with comma-ok assertions, you're doing it the hard way. Go provides a type switch, which is a much cleaner way to handle a variety of possible types. It's essentially a switch statement specifically designed for type assertions.
I use this all the time when building flexible data processors. Here's how it looks in practice:
func processValue(i any) {
switch v := i.(type) {
case int:
fmt.Printf("Processing an integer: %d\n", v * 2)
case string:
fmt.Printf("Processing a string: %s\n", v)
case bool:
fmt.Printf("Processing a boolean: %t\n", v)
default:
fmt.Println("Unknown type!")
}
}
📋 Practical Task
Build a Heterogeneous Data Summarizer
Your task is to create a program that can process a slice of mixed data types and produce a summary. Imagine you are receiving a stream of mixed logs containing both IDs (integers) and Messages (strings).
- Create a slice of
any(orinterface{}) containing at least five elements: a mix of strings and integers. - Write a function called
summarizethat takes this slice as an argument. - Inside
summarize, use a type switch to:- Sum up all the integers found in the slice.
- Concatenate all the strings into one long sentence (separated by spaces).
- The function should print the final total sum and the final concatenated string.
- Ensure that any types that are neither
intnorstringare simply ignored.
There are no comments for now.