Modern Go Collection Patterns for Slices and Maps: A Complete Go 1.21+ Guide
Go 1.21 introduced standardized collection utilities in the slices and maps packages that replace manual loops with declarative functions like slices.Contains, maps.Clone, and slices.Compact.
The JetBrains/go-modern-guidelines repository catalogs these idiomatic patterns to help developers write cleaner, more maintainable Go code. According to the project's FEATURES.md, these utilities eliminate boilerplate for common operations on slices and maps, reducing the need for error-prone manual iteration.
Slices Package Patterns
The slices package, introduced in Go 1.21, provides generic functions that operate on slices of any ordered or comparable type.
slices.Contains: Replace Manual Search Loops
Instead of iterating with a for loop to check membership, use slices.Contains for O(n) existence checks.
// Before: Manual iteration
found := false
for _, v := range s {
if v == needle {
found = true
break
}
}
// After: Declarative check
found := slices.Contains(s, needle)
As documented in the JetBrains/go-modern-guidelines, this pattern eliminates the need for explicit loop control variables and break statements.
slices.Sort and slices.SortFunc: Simplified Sorting
The legacy sort.Slice requires reflection and interface conversions. The modern slices.SortFunc uses generics for type-safe comparisons with cmp.Compare.
// Before: sort.Slice with reflection overhead
sort.Slice(users, func(i, j int) bool {
return users[i].Name < users[j].Name
})
// After: Type-safe generic sorting
slices.SortFunc(users, func(a, b User) int {
return cmp.Compare(a.Name, b.Name)
})
For primitive types that satisfy constraints.Ordered, use slices.Sort directly without a comparator function.
slices.Index: Find Element Positions
Replace manual index tracking with slices.Index to return the first index of a matching element, or -1 if absent.
// Before: Counter-based search
idx := -1
for i, v := range s {
if v == needle {
idx = i
break
}
}
// After: Direct index lookup
idx := slices.Index(s, needle)
This pattern is cataloged under the range-over-index guideline in the repository.
slices.Reverse: In-Place Reversal
Eliminate explicit swap loops when reversing slice order.
// Before: Manual swap loop
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
// After: Single function call
slices.Reverse(s)
slices.Compact: Remove Consecutive Duplicates
Use slices.Compact to filter adjacent duplicate values without maintaining a separate seen-map.
// Before: Conditional append logic
result := s[:1]
for i := 1; i < len(s); i++ {
if s[i] != s[i-1] {
result = append(result, s[i])
}
}
// After: Compact consecutive duplicates
result := slices.Compact(s)
Note that slices.Compact modifies the slice in place and returns a truncated slice; it does not eliminate non-consecutive duplicates.
Maps Package Patterns
The maps package provides generic utilities for copying and manipulating map collections.
maps.Clone: Efficient Map Copying
Replace manual allocation and iteration when creating independent copies of maps.
// Before: Manual copy loop
copy := make(map[string]int, len(m))
for k, v := range m {
copy[k] = v
}
// After: One-line clone operation
copy := maps.Clone(m)
According to FEATURES.md, maps.Clone handles nil maps safely and optimizes allocation based on the source map's size.
maps.DeleteFunc: Conditional Deletion
Remove entries matching a predicate without manual range loops that require map modification safety checks.
// Before: Range with delete (requires care with map modification)
for k, v := range m {
if shouldDelete(k, v) {
delete(m, k)
}
}
// After: Declarative conditional deletion
maps.DeleteFunc(m, shouldDelete)
maps.Keys and maps.Values: Extract Collections
While maps.Keys and maps.Values return iterators, combine them with slices.Collect (Go 1.23+) to materialize slices.
// Before: Manual slice construction
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
// After: Iterator to slice conversion (Go 1.23+)
keys := slices.Collect(maps.Keys(m))
vals := slices.Collect(maps.Values(m))
Note that slices.Collect requires Go 1.23 or later, though maps.Keys and maps.Values are available in Go 1.21+ as iterators.
The clear Builtin: Reset Collections
Go 1.21 introduced the clear builtin, which efficiently empties maps or zeroes slice elements.
// Before: Map deletion loop
for k := range m {
delete(m, k)
}
// After: Clear all entries
clear(m)
// Before: Slice zeroing
for i := range s {
s[i] = zero
}
// After: Zero all elements
clear(s)
This applies to both maps and slices, replacing explicit deletion or zeroing loops as documented in the clear collection pattern.
Summary
- Use
slices.Containsinstead of manualforloops to check element existence in slices. - Prefer
slices.SortFuncoversort.Slicefor type-safe, generic sorting without reflection overhead. - Adopt
maps.Cloneto duplicate maps in a single line without manual iteration. - Leverage
maps.DeleteFuncfor predicate-based entry removal without loop complexity. - Apply
clearto empty maps or zero-out slices efficiently in O(n) time. - Combine
maps.Keys/Valueswithslices.Collect(Go 1.23+) to extract keys or values into slices. - Use
slices.Compactandslices.Reversefor in-place slice transformations that previously required manual index manipulation.
Frequently Asked Questions
What Go version introduced the slices and maps packages?
The slices and maps packages were introduced in Go 1.21 as part of the standard library's generics expansion. While slices.Collect requires Go 1.23, the core collection utilities are available from Go 1.21 onward.
How do slices.Sort and the legacy sort.Slice differ?
slices.Sort and slices.SortFunc use generics to accept the slice directly and a comparison function with concrete types, eliminating the reflection and interface{} conversions required by sort.Slice. This results in better performance and compile-time type safety.
Is slices.Collect available in Go 1.21?
No, slices.Collect was introduced in Go 1.23 to convert iterators into slices. In Go 1.21 and 1.22, you must manually append from maps.Keys or maps.Values iterators to build slices.
How does maps.Clone handle nil maps?
maps.Clone returns nil when passed a nil map, maintaining the same behavior as manually checking for nil before allocation. This prevents panic conditions and matches the zero-value semantics of the original map.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →