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
175: Comparable Constraint Usage
I see this happen all the time when developers first dive into Go generics: they see the built-in comparable constraint and assume it's a "catch-all" for any type that can be compared in any way. Specifically, there's a common belief that if a type satisfies comparable, you can use it for sorting or finding the maximum value in a list.
"Comparable means I can use the greater-than operator"
It sounds intuitive, right? If I can compare two things, I should be able to tell which one is "bigger." But in Go, comparable is much narrower than that. It only guarantees that the == and != operators work. It tells absolutely nothing about <, >, <=, or >=.
Let's look at where this falls apart. If you try to write a generic Max function using comparable, the compiler will shut you down immediately:
type Number interface {
comparable
}
func Max[T Number](a, b T) T {
if a > b { // ❌ Compiler Error: invalid operation: a > b (type T does not support operator >)
return a
}
return b
}
The compiler is being pedantic here because it has to be. Think about comparable types that aren't numbers—like structs (provided their fields are also comparable) or booleans. While you can check if two structs are identical, it makes no sense to ask if one struct is "greater than" another without a custom definition. Go refuses to guess your intent.
The strict boundary between equality and order
To use comparable correctly, you have to shift your mindset: use it when you are checking for identity, not magnitude. The most common real-world application of comparable is when you're building something that behaves like a map or a set, where you just need to know "is this the same item I saw before?"
I usually reach for comparable when I'm writing utility functions that search through slices or filter out duplicates. Since map keys must be comparable, comparable is the perfect constraint for any generic function that uses a map internally to track occurrences.
Here is a proper way to use it. Let's say we want a function that finds the first index of a specific element in any slice. We don't care if the elements can be sorted; we just care if they are equal:
func FindIndex[T comparable](slice []T, target T) int {
for i, v := range slice {
if v == target { // ✅ This is exactly what comparable is for
return i
}
}
return -1
}
// Usage
names := []string{"Alice", "Bob", "Charlie"}
idx := FindIndex(names, "Bob") // Returns 1
ids := []int{101, 202, 303}
idx2 := FindIndex(ids, 202) // Returns 1
If you actually need to use < or >, you can't use the built-in comparable. You'll need to use the cmp.Ordered constraint from the standard library's cmp package (or define your own interface that covers int, float64, string, etc.). Just remember: comparable is for equality. Period.
📋 Practical Task
Build a Generic Duplicate Element Detector
Your task is to create a generic function called HasDuplicates. This function should take a slice of any type T and return a boolean indicating whether any element appears more than once in the slice.
Requirements:
- The function must use the
comparableconstraint to ensure the typeTcan be used as a map key. - Use a
map[T]bool(or a map to an empty structmap[T]struct{}) to track the elements you have already encountered while iterating through the slice. - The function should return
trueas soon as the first duplicate is found, andfalseif the loop completes without finding any duplicates.
Test your implementation with a slice of strings (e.g., []string{"apple", "banana", "apple"}) and a slice of integers (e.g., []int{1, 2, 3, 4}) to ensure the generic constraint is working correctly across different types.
There are no comments for now.