SKILL.md
$28
- Type-safe generics — no
interface{}casts, no reflection, compile-time checking, no interface boxing overhead
- Immutable by default — returns new collections, safe for concurrent reads, easier to reason about
- Composable — functions take and return slices/maps, so they chain without wrapper types
- Zero dependencies — only Go stdlib, no transitive dependency risk
- Progressive complexity — start with
lo, upgrade tolop/lom/loionly when profiling demands it
- Error variants — most functions have
Errsuffixes (MapErr,FilterErr,ReduceErr) that stop on first error
Installation
go get github.com/samber/lo
Package
Import
Alias
Go version
Core (immutable)
github.com/samber/lo
lo
1.18+
Parallel
github.com/samber/lo/parallel
lop
1.18+
Mutable
github.com/samber/lo/mutable
lom
1.18+
Iterator
github.com/samber/lo/it
loi
1.23+
SIMD (experimental)
github.com/samber/lo/exp/simd
—
1.25+ (amd64 only)
Choose the Right Package
Start with lo. Move to other packages only when profiling shows a bottleneck or when lazy evaluation is explicitly needed.
Package
Use when
Trade-off
lo
Default for all transforms
Allocates new collections (safe, predictable)
lop
CPU-bound work on large datasets (1000+ items)
Goroutine overhead; not for I/O or small slices
lom
Hot path confirmed by pprof -alloc_objects
Mutates input — caller must understand side effects
loi
Large datasets with chained transforms (Go 1.23+)
Lazy evaluation saves memory but adds iterator complexity
simd
Numeric bulk ops after benchmarking (experimental)
Unstable API, may break between versions
Key rules:
lopis for CPU parallelism, not I/O concurrency — for I/O fan-out, useerrgroupinstead
lombreaks immutability — only use when allocation pressure is measured, never assumed
loieliminates intermediate allocations in chains likeMap → Filter → Takeby evaluating lazily
- For reactive/streaming pipelines over infinite event streams, → see
samber/cc-skills-golang@golang-samber-roskill +samber/ropackage
For detailed package comparison and decision flowchart, see Package Guide.
Core Patterns
Transform a slice
// ✓ lo — declarative, type-safe
names := lo.Map(users, func(u User, _ int) string {
return u.Name
})
// ✗ Manual — boilerplate, error-prone
names := make([]string, 0, len(users))
for _, u := range users {
names = append(names, u.Name)
}
Filter + Reduce
total := lo.Reduce(
lo.Filter(orders, func(o Order, _ int) bool {
return o.Status == "paid"
}),
func(sum float64, o Order, _ int) float64 {
return sum + o.Amount
},
0,
)
GroupBy
byStatus := lo.GroupBy(tasks, func(t Task, _ int) string {
return t.Status
})
// map[string][]Task{"open": [...], "closed": [...]}
Error variant — stop on first error
results, err := lo.MapErr(urls, func(url string, _ int) (Response, error) {
return http.Get(url)
})
Common Mistakes
Mistake
Why it fails
Fix
Using lo.Contains when slices.Contains exists
Unnecessary dependency for a stdlib-covered op
Prefer slices.Contains/slices.Sort since Go 1.21+ and slices.Collect(maps.Keys(m)) since Go 1.23+ when a key slice is needed
Using lop.Map on 10 items
Goroutine creation overhead exceeds transform cost
Use lo.Map — lop benefits start at ~1000+ items for CPU-bound work
Assuming lo.Filter modifies the input
lo is immutable by default — it returns a new slice
Use lom.Filter if you explicitly need in-place mutation
Using lo.Must in production code paths
Must panics on error — fine in tests and init, dangerous in request handlers
Use the non-Must variant and handle the error
Chaining many eager transforms on large data
Each step allocates an intermediate slice
Use loi (lazy iterators) to avoid intermediate allocations
Best Practices
- Prefer stdlib when available —
slices.Containsandslices.Sort(Go 1.21+) carry no dependency;maps.Keysis Go 1.23+ and returns an iterator, so useslices.Collect(maps.Keys(m))when you need a slice. Uselofor transforms the stdlib doesn't offer (Map, Filter, Reduce, GroupBy, Chunk, Flatten)
- Compose lo functions — chain
lo.Filter→lo.Map→lo.GroupByinstead of writing nested loops. Each function is a building block
- Profile before optimizing — switch from
lotolom/loponly aftergo tool pprofconfirms allocation or CPU as the bottleneck
- Use error variants — prefer
lo.MapErroverlo.Map+ manual error collection. Error variants stop early and propagate cleanly
- **Use
lo.Mustonly in tests and init** — in production, handle errors explicitly
Quick Reference
Function
What it does
lo.Map
Transform each element
lo.Filter / lo.Reject
Keep / remove elements matching predicate
lo.Reduce
Fold elements into a single value
lo.ForEach
Side-effect iteration
lo.GroupBy
Group elements by key
lo.Chunk
Split into fixed-size batches
lo.Flatten
Flatten nested slices one level
lo.Uniq / lo.UniqBy
Remove duplicates
lo.Find / lo.FindOrElse
First match or default
lo.Contains / lo.Every / lo.Some
Membership tests
lo.Keys / lo.Values
Extract map keys or values
lo.PickBy / lo.OmitBy
Filter map entries
lo.Zip2 / lo.Unzip2
Pair/unpair two slices
lo.Range / lo.RangeFrom
Generate number sequences
lo.Ternary / lo.If
Inline conditionals
lo.ToPtr / lo.FromPtr
Pointer helpers
lo.Must / lo.Try
Panic-on-error / recover-as-bool
lo.Async / lo.Attempt
Async execution / retry with backoff
lo.Debounce / lo.Throttle
Rate limiting
lo.ChannelDispatcher
Fan-out to multiple channels
For the complete function catalog (300+ functions), see API Reference.
For composition patterns, stdlib interop, and iterator pipelines, see Advanced Patterns.
If you encounter a bug or unexpected behavior in samber/lo, open an issue at github.com/samber/lo/issues.
Cross-References
- → See
samber/cc-skills-golang@golang-samber-roskill for reactive/streaming pipelines over infinite event streams (samber/ropackage)
- → See
samber/cc-skills-golang@golang-samber-moskill for monadic types (Option, Result, Either) that compose with lo transforms
- → See
samber/cc-skills-golang@golang-data-structuresskill for choosing the right underlying data structure
- → See
samber/cc-skills-golang@golang-performanceskill for profiling methodology before switching tolom/lop