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
60: Generics in Go: Type Parameters
Wait, why do I need type parameters? Can't I just use "any"?
I get this question a lot. If you've been using any (which is just an alias for interface{}), you know the pain: you lose your type safety. Every time you pull a value out of an any slice or map, you have to use a type assertion like val.(int). It's tedious, and it's a runtime crash waiting to happen if you guess the type wrong.
Type parameters let us write code that is generic but remains statically typed. Instead of saying "this function takes any type," we say "this function takes a type T, and whatever T happens to be when the function is called, it will be consistent throughout the entire operation."
// Without generics, we'd need a version for int, one for float64, etc.
// With type parameters, we define [T int | float64]
func Max[T int | float64](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(10, 20)) // T is inferred as int
fmt.Println(Max(3.14, 2.71)) // T is inferred as float64
}
Notice how I didn't have to cast anything? The compiler knows that if I pass in two floats, I'm getting a float back.
How do I restrict which types can be used as parameters?
You can't just use any random type for every generic function. For example, in the Max function above, I used >. You can't use > on a struct or a map. That's where constraints come in.
A constraint is just an interface that defines the set of permissible types. While I used a simple union (int | float64) above, for real-world projects, I usually define a named interface to keep things clean. I also frequently use the built-in comparable constraint, which tells Go that the type must support == and !=.
type Number interface {
int | int64 | float64
}
// This function only accepts types that satisfy the Number interface
func Sum[T Number](slice []T) T {
var total T
for _, v := range slice {
total += v
}
return total
}
If you try to pass a slice of strings into Sum, the code won't even compile. I prefer this over a runtime panic any day of the week.
Can I use these in structs, or just functions?
Absolutely. This is where generics really start to shine, especially when you're building data structures. Think about a Response wrapper for an API. You always have the same metadata (status, message), but the Data field changes depending on the endpoint.
Before generics, you'd make Data any. Now, you can make the entire struct generic.
type APIResponse[T any] struct {
Status int
Message string
Data T
}
type User struct {
Name string
Age int
}
func main() {
// A response specifically for a User
userResp := APIResponse[User]{
Status: 200,
Message: "Success",
Data: User{Name: "Alice", Age: 30},
}
// A response specifically for a list of strings
tagsResp := APIResponse[[]string]{
Status: 200,
Message: "Success",
Data: []string{"golang", "generics", "coding"},
}
}
I love this pattern because it documents exactly what the response should contain without forcing the caller to perform unsafe type assertions every time they access the Data field.
📋 Practical Task
Build a Generic UniqueSet
In Go, we often use a map[T]bool or map[T]struct{} to simulate a "Set" (a collection of unique elements). Your task is to wrap this logic into a generic data structure so it can be reused for any comparable type.
Requirements:
- Create a generic struct called
UniqueSet[T comparable]that holds a map internally. - Implement a method
Add(value T)that adds an element to the set. - Implement a method
Contains(value T) boolthat checks if an element exists. - Implement a method
Count() intthat returns the number of unique elements.
Test your implementation by creating one set for int and another set for string to ensure the type parameters are working correctly.
There are no comments for now.