evals/evals.json
{
"skill_name": "golang-samber-lo",
"evals": [
{
"id": 1,
"name": "lop-for-io-trap",
"prompt": "I have a Go service that needs to fetch data from 50 different REST APIs concurrently and collect results. I'm using samber/lo already. Should I use lop.Map to parallelize the HTTP calls? Write the implementation.",
"expected_output": "Should recommend errgroup (or similar) for I/O-bound concurrency instead of lop. lop is for CPU-bound parallelism. Should explain why lop is wrong here (no context cancellation, no error handling, goroutine-per-element overhead for I/O).",
"assertions": [
{"text": "Recommends errgroup or similar I/O concurrency pattern instead of (or in addition to) lop for HTTP calls", "type": "semantic"},
{"text": "Explains that lop is designed for CPU-bound parallelism, not I/O-bound work", "type": "semantic"},
{"text": "Mentions lack of context cancellation as a limitation of lop for I/O", "type": "semantic"},
{"text": "Does NOT simply use lop.Map as the primary solution for HTTP fan-out", "type": "semantic"},
{"text": "Shows working Go code for the concurrent HTTP fetch pattern", "type": "semantic"}
]
},
{
"id": 2,
"name": "premature-lom-optimization",
"prompt": "My Go API is a bit slow. I'm using lo.Filter and lo.Map in several places. I want to switch everything to lom.Filter and lom.Map for better performance. Can you help me refactor?",
"expected_output": "Should advise profiling first before switching to lom. Should warn about immutability loss. Should NOT blindly refactor everything to lom.",
"assertions": [
{"text": "Recommends profiling (pprof, alloc_objects) before switching to lom", "type": "semantic"},
{"text": "Warns that lom mutates the input slice (breaks immutability)", "type": "semantic"},
{"text": "Does NOT blindly refactor all lo calls to lom without questioning the premise", "type": "semantic"},
{"text": "Explains that the performance issue may not be caused by lo allocations", "type": "semantic"},
{"text": "Mentions that lom is only appropriate for hot paths confirmed by profiling", "type": "semantic"},
{"text": "Warns about concurrency safety implications of switching to mutable operations", "type": "semantic"}
]
},
{
"id": 3,
"name": "stdlib-vs-lo-preference",
"prompt": "I'm writing a Go function that needs to check if a string slice contains a value, sort an int slice, and get all keys from a map. I already have samber/lo as a dependency. Should I use lo.Contains, and lo.Keys for these?",
"expected_output": "Should recommend stdlib slices.Contains and slices.Sort over lo equivalents when using Go 1.21+. For map keys, should gate maps.Keys to Go 1.23+ and use slices.Collect(maps.Keys(m)) when a slice is needed. Should explain that stdlib is preferred for operations it covers.",
"assertions": [
{"text": "Recommends slices.Contains from stdlib over lo.Contains", "type": "semantic"},
{"text": "Recommends slices.Sort or slices.SortFunc from stdlib", "type": "semantic"},
{"text": "Recommends slices.Collect(maps.Keys(m)) from stdlib over lo.Keys when the module targets Go 1.23+", "type": "semantic"},
{"text": "Mentions Go 1.21+ for slices helpers and Go 1.23+ for maps.Keys", "type": "semantic"},
{"text": "Explains the rationale: prefer stdlib when it covers the operation to avoid unnecessary dependency usage", "type": "semantic"},
{"text": "Acknowledges that lo is still useful for operations stdlib doesn't provide (Map, Filter, Reduce, GroupBy)", "type": "semantic"}
]
},
{
"id": 4,
"name": "loi-go-version-constraint",
"prompt": "I'm building a data pipeline in Go 1.20 that chains multiple transformations on a large dataset (1M records). I want to use lo/it for lazy evaluation to avoid intermediate allocations. Show me how.",
"expected_output": "Should warn that lo/it requires Go 1.23+ and cannot be used with Go 1.20. Should suggest alternatives.",
"assertions": [
{"text": "Warns that lo/it (loi) requires Go 1.23+ and is not available in Go 1.20", "type": "semantic"},
{"text": "Suggests upgrading Go version to 1.23+ to use lo/it", "type": "semantic"},
{"text": "Provides alternative approaches for Go 1.20 (e.g., compose lo functions, manual pipeline, or accept intermediate allocations)", "type": "semantic"},
{"text": "Does NOT provide lo/it code that won't compile on Go 1.20", "type": "semantic"},
{"text": "Mentions that loi uses range-over-func which is a Go 1.23 feature", "type": "semantic"}
]
},
{
"id": 5,
"name": "must-init-scope-boundary",
"prompt": "I'm setting up a Go application. I use lo.Must in two places: (1) in main() to parse a required config file at startup, and (2) in an HTTP handler to parse each incoming request body. A teammate says both uses are wrong. Is that right?",
"expected_output": "Should distinguish: lo.Must is acceptable in main()/init() for startup failures where panicking is appropriate (app can't run without valid config). It is NOT acceptable in HTTP handlers where panics crash the request and potentially the server. The teammate is wrong about the init use case.",
"assertions": [
{"text": "Correctly identifies that lo.Must in main() for startup config parsing IS acceptable", "type": "semantic"},
{"text": "Correctly identifies that lo.Must in the HTTP handler is NOT acceptable (panics on each bad request)", "type": "semantic"},
{"text": "Does NOT blanket-reject all uses of Must — init/startup usage is valid", "type": "semantic"},
{"text": "Explains the reasoning: startup panics are appropriate failure modes; request panics are not", "type": "semantic"},
{"text": "Shows proper error handling in the HTTP handler (if err != nil with http.Error response)", "type": "semantic"}
]
},
{
"id": 6,
"name": "import-aliases-knowledge",
"prompt": "I want to use the parallel, mutable, and iterator sub-packages of samber/lo in my Go project. What are the correct import paths and conventional aliases?",
"expected_output": "Should list all import paths with their conventional aliases: lop, lom, loi.",
"assertions": [
{"text": "Lists github.com/samber/lo/parallel with alias lop", "type": "semantic"},
{"text": "Lists github.com/samber/lo/mutable with alias lom", "type": "semantic"},
{"text": "Lists github.com/samber/lo/it with alias loi", "type": "semantic"},
{"text": "Mentions that lo/it requires Go 1.23+", "type": "semantic"},
{"text": "Lists the core package github.com/samber/lo with alias lo", "type": "semantic"}
]
},
{
"id": 7,
"name": "immutability-trap",
"prompt": "I have this Go code using samber/lo:\n\nusers := []User{{Name: \"Alice\", Active: true}, {Name: \"Bob\", Active: false}}\nlo.Filter(users, func(u User, _ int) bool { return u.Active })\nfmt.Println(len(users)) // What does this print?\n\nWill the users slice be modified after the Filter call?",
"expected_output": "Should clearly state that lo.Filter does NOT modify the input slice. The original users slice still has 2 elements. Must mention immutability-by-default.",
"assertions": [
{"text": "States that lo.Filter returns a NEW slice and does NOT modify the input", "type": "semantic"},
{"text": "Correctly says len(users) still prints 2", "type": "semantic"},
{"text": "Explains that lo is immutable by default — all core functions return new collections", "type": "semantic"},
{"text": "Points out the bug: the return value of lo.Filter is discarded and should be assigned", "type": "semantic"},
{"text": "Mentions lom.Filter as the alternative if in-place mutation is desired", "type": "semantic"}
]
},
{
"id": 8,
"name": "error-variant-awareness",
"prompt": "I'm using samber/lo to transform a slice of URLs into responses by making HTTP calls. Some calls might fail. Currently I'm using lo.Map and collecting errors manually in a separate slice. Is there a better way?",
"expected_output": "Should recommend lo.MapErr which stops on first error, or lo.FilterMap for skipping errors. Should explain error variant pattern.",
"assertions": [
{"text": "Recommends lo.MapErr as the error-aware variant of lo.Map", "type": "semantic"},
{"text": "Explains that MapErr stops processing on the first error and returns (result, error)", "type": "semantic"},
{"text": "Shows the MapErr function signature or usage example", "type": "semantic"},
{"text": "Mentions that most lo functions have Err suffixes (FilterErr, ReduceErr, etc.)", "type": "semantic"},
{"text": "Alternatively suggests lo.FilterMap if the goal is to skip errors rather than stop", "type": "semantic"}
]
},
{
"id": 9,
"name": "simd-production-stability",
"prompt": "I want to use lo/exp/simd for optimizing numeric array operations in my production Go service. It processes millions of float64 values. Is this a good idea?",
"expected_output": "Should warn that simd is experimental, API may break between versions, not covered by semver stability. Recommend benchmarking first.",
"assertions": [
{"text": "Warns that lo/exp/simd is experimental and API may break between versions", "type": "semantic"},
{"text": "States it is NOT covered by semver stability guarantees", "type": "semantic"},
{"text": "Recommends benchmarking to verify actual performance gains before adopting", "type": "semantic"},
{"text": "Suggests version pinning if used despite experimental status", "type": "semantic"},
{"text": "Does NOT unconditionally recommend simd for production use", "type": "semantic"}
]
},
{
"id": 10,
"name": "streaming-redirect-to-ro",
"prompt": "I need to build a reactive data pipeline in Go that processes an infinite stream of events from Kafka, applies transformations, and publishes results. I'm already using samber/lo for batch transforms. Can I use lo for this streaming use case?",
"expected_output": "Should redirect to samber/ro for reactive/streaming pipelines. Should explain that lo is for finite/batch transforms, not infinite streams.",
"assertions": [
{"text": "Recommends samber/ro for reactive/streaming pipelines over infinite event streams", "type": "semantic"},
{"text": "Explains that lo is designed for finite/batch collection transforms, not infinite streams", "type": "semantic"},
{"text": "Mentions the golang-samber-ro skill or samber/ro package by name", "type": "semantic"},
{"text": "Does NOT attempt to use lo.Map/lo.Filter for infinite stream processing", "type": "semantic"},
{"text": "Explains the conceptual difference: lo = batch/finite, ro = reactive/infinite", "type": "semantic"}
]
},
{
"id": 11,
"name": "lop-threshold-cpu-vs-io",
"prompt": "I have two use cases in my Go service both using samber/lo. (A) I'm resizing 20 images in memory — each resize takes ~200ms of CPU. (B) I'm extracting the 'name' field from 50 user structs in memory. My teammate says I should switch both to lop for better performance. What do you think?",
"expected_output": "Case A (image resizing): lop is appropriate — CPU-bound work, substantial per-item cost, enough items to justify goroutine overhead. Case B (field extraction): lop is wrong — trivially cheap operation on a small dataset; goroutine overhead exceeds benefit. The teammate is only right about case A.",
"assertions": [
{"text": "Approves lop.Map for case A (CPU-bound image resizing with ~200ms per item)", "type": "semantic"},
{"text": "Rejects lop.Map for case B (trivial field extraction on 50 items)", "type": "semantic"},
{"text": "Distinguishes CPU-bound work (lop appropriate) from trivial/cheap operations (lo sufficient)", "type": "semantic"},
{"text": "Mentions that lop overhead only pays off for CPU-bound work on datasets of ~1000+ items OR expensive per-item operations", "type": "semantic"},
{"text": "Does NOT recommend lop for both use cases indiscriminately", "type": "semantic"}
]
},
{
"id": 12,
"name": "filtermap-vs-maperr-for-partial-failures",
"prompt": "I have a []string of user-supplied date strings. I need to parse each one with time.Parse, keep the successfully parsed dates, and silently discard parse failures (not stop on first error). I'm using samber/lo. What's the right approach?",
"expected_output": "Should recommend lo.FilterMap (not lo.MapErr which stops on first error, and not lo.Filter+lo.Map which creates an intermediate slice). FilterMap maps and keeps only successful results in a single pass. The key trap is that MapErr stops on the first error, which is the opposite of what's wanted here.",
"assertions": [
{"text": "Recommends lo.FilterMap for map-and-discard-failures in a single pass", "type": "semantic"},
{"text": "Does NOT recommend lo.MapErr (which stops on first error, not what's wanted)", "type": "semantic"},
{"text": "Shows FilterMap with a (time.Time, bool) return where bool is false on parse failure", "type": "semantic"},
{"text": "Explains that FilterMap silently discards items where the bool is false", "type": "semantic"},
{"text": "Does NOT chain lo.Filter + lo.Map as the primary solution (creates intermediate slice)", "type": "semantic"}
]
},
{
"id": 13,
"name": "channel-dispatcher-load-balancing-strategy",
"prompt": "I have a Go service that distributes tasks to 4 worker goroutines via channels. Workers have different processing speeds — some tasks go to a fast worker (handles 3x more), others to slower workers. I'm using samber/lo and already know about lo.ChannelDispatcher. Which dispatch strategy should I use so that faster workers receive proportionally more tasks?",
"expected_output": "Should recommend WeightedRandom strategy. RoundRobin ignores worker speed. Random is uniform. WeightedRandom lets you assign relative weights matching worker capacity. Should show how to configure weights.",
"assertions": [
{"text": "Recommends the WeightedRandom dispatch strategy (not RoundRobin or Random)", "type": "semantic"},
{"text": "Explains that RoundRobin distributes evenly regardless of worker capacity", "type": "semantic"},
{"text": "Explains that WeightedRandom allows assigning proportional weights to each output channel", "type": "semantic"},
{"text": "Shows lo.ChannelDispatcher usage with the WeightedRandom strategy and weights", "type": "semantic"},
{"text": "Does NOT recommend RoundRobin as the primary solution for unequal-capacity workers", "type": "semantic"},
{"text": "Shows or describes setting weights where the fast worker gets 3x the weight of slow workers", "type": "semantic"}
]
},
{
"id": 14,
"name": "v2-version-trap",
"prompt": "I want to upgrade my project from samber/lo v1 to v2 for the latest features. What's the migration path? Are there breaking changes?",
"expected_output": "Should clarify that there is no v2 of samber/lo. The library is on v1 with semver stability guarantees.",
"assertions": [
{"text": "States that samber/lo v2 does not exist — the library is on v1", "type": "semantic"},
{"text": "Mentions that v1 follows semantic versioning with no breaking changes before v2", "type": "semantic"},
{"text": "Provides the correct install command: go get github.com/samber/lo@v1", "type": "semantic"},
{"text": "Does NOT fabricate v2 migration steps or breaking changes", "type": "semantic"}
]
},
{
"id": 15,
"name": "lazy-chain-intermediate-allocations",
"prompt": "I have a pipeline in Go that processes 1M records: filter by status, map to extract fields, group by category, then take the top 10 from each group. Using samber/lo, each step creates an intermediate slice. How can I avoid these intermediate allocations?",
"expected_output": "Should recommend lo/it (loi) for lazy evaluation to eliminate intermediate allocations. Should explain the lazy pipeline pattern.",
"assertions": [
{"text": "Recommends lo/it (loi) for lazy evaluation to avoid intermediate slice allocations", "type": "semantic"},
{"text": "Explains that loi pipelines process elements on-demand without buffering intermediate results", "type": "semantic"},
{"text": "Mentions Go 1.23+ requirement for loi", "type": "semantic"},
{"text": "Shows or describes a lazy pipeline using loi functions", "type": "semantic"},
{"text": "Contrasts eager (lo) vs lazy (loi) approach in terms of memory allocation", "type": "semantic"}
]
},
{
"id": 16,
"name": "lo-zero-dependencies",
"prompt": "My team is concerned about adding samber/lo as a dependency. They worry about transitive dependencies and supply chain risk. What dependencies does samber/lo pull in?",
"expected_output": "Should state that samber/lo has zero external runtime dependencies — it relies only on Go's standard library.",
"assertions": [
{"text": "States that samber/lo has zero external/runtime dependencies", "type": "semantic"},
{"text": "Mentions it relies only on Go's standard library", "type": "semantic"},
{"text": "Addresses the supply chain concern by noting minimal transitive dependency risk", "type": "semantic"},
{"text": "Does NOT claim lo has external dependencies", "type": "semantic"}
]
},
{
"id": 17,
"name": "ternary-side-effect-ordering",
"prompt": "I'm refactoring a Go function to use samber/lo for conciseness. I have this code:\n\n```go\nvar label string\nif isAdmin {\n label = formatAdminLabel(user) // calls DB, expensive\n} else {\n label = user.Name\n}\n```\n\nI rewrote it as:\n\n```go\nlabel := lo.Ternary(isAdmin, formatAdminLabel(user), user.Name)\n```\n\nMy colleague says this is a bug. Are they right?",
"expected_output": "Yes, the colleague is right. lo.Ternary is a regular Go function call — both arguments are evaluated before the function is called. formatAdminLabel(user) runs unconditionally even when isAdmin is false. Should recommend lo.TernaryF with closures so only the winning branch executes.",
"assertions": [
{"text": "Confirms the colleague is correct — this is indeed a bug", "type": "semantic"},
{"text": "Explains that lo.Ternary evaluates BOTH branches before the function is called (Go's eager argument evaluation)", "type": "semantic"},
{"text": "Identifies that formatAdminLabel(user) runs even when isAdmin is false", "type": "semantic"},
{"text": "Recommends lo.TernaryF with closures: lo.TernaryF(isAdmin, func() string { return formatAdminLabel(user) }, func() string { return user.Name })", "type": "semantic"}
]
}
]
}
references/advanced-patterns.md
# Advanced Patterns
## Composing Transformations
Chain lo functions to build multi-step pipelines. Each function returns a new collection that feeds into the next.
```go
// Pipeline: extract active user emails grouped by role
emailsByRole := lo.GroupBy(
lo.Map(
lo.Filter(users, func(u User, _ int) bool {
return u.Active && u.EmailVerified
}),
func(u User, _ int) UserEmail {
return UserEmail{Role: u.Role, Email: u.Email}
},
),
func(ue UserEmail, _ int) string {
return ue.Role
},
)
```
For long chains, break into named intermediate variables for readability:
```go
active := lo.Filter(users, func(u User, _ int) bool {
return u.Active
})
names := lo.Map(active, func(u User, _ int) string {
return u.Name
})
unique := lo.Uniq(names)
```
## lo + stdlib Interop
Prefer stdlib when it covers the operation — `lo` adds value for functional transforms the stdlib doesn't provide.
| Operation | stdlib (prefer) | lo (use when stdlib lacks) |
| --- | --- | --- |
| Contains | `slices.Contains(s, v)` | `lo.ContainsBy(s, fn)` — predicate-based |
| Sort | `slices.SortFunc(s, cmp)` | — (lo doesn't provide sort) |
| Keys | `maps.Keys(m)` | `lo.UniqKeys(m)` — deduplicated keys |
| Clone | `slices.Clone(s)` | `lo.Map(s, fn)` — when you need transform during clone |
| Min/Max | `slices.Min(s)` | `lo.MinBy(s, fn)` — by extractor function |
**Rule of thumb:** If `slices.*` or `maps.*` does what you need, use it. Reach for `lo` when you need predicates, transforms, grouping, or error variants.
## lo + samber/mo Integration
`samber/mo` provides monadic types (Option, Result, Either). They compose naturally with lo:
```go
// Filter users with valid optional emails
validEmails := lo.FilterMap(users, func(u User, _ int) (string, bool) {
email, ok := u.Email.Get() // mo.Option[string]
return email, ok
})
// Map with Result — collect successes
results := lo.FilterMap(urls, func(url string, _ int) (Response, bool) {
res := fetchURL(url) // returns mo.Result[Response]
val, err := res.Get()
return val, err == nil
})
```
## Iterator Patterns (loi)
Requires Go 1.23+. Lazy iterators avoid intermediate allocations.
### Eager vs lazy comparison
```go
// Eager — allocates 2 intermediate slices
result := lo.Map(lo.Filter(bigSlice, filterFn), mapFn)
// Lazy — zero intermediate allocations
for v := range loi.Map(loi.Filter(bigSlice, filterFn), mapFn) {
process(v)
}
```
### Building lazy pipelines
```go
// Lazy pipeline: filter → map → take first 10
pipeline := loi.Take(
loi.Map(
loi.Filter(records, func(r Record) bool {
return r.Score > 0.8
}),
func(r Record) string {
return r.Name
},
),
10,
)
// Consume with range
for name := range pipeline {
fmt.Println(name)
}
```
## Performance-Sensitive Patterns
### When to switch from lo to lom
**Trigger:** `go tool pprof -alloc_objects` shows `lo.Filter` or `lo.Map` as top allocators in a hot path.
```go
// Before — allocates new slice every call
filtered := lo.Filter(events, isValid)
// After — zero allocations, modifies in-place
events = lom.Filter(events, isValid)
// Warning: 'events' is now modified. Original data is lost.
```
### Parallel transforms
**Trigger:** `go tool pprof -cpu` shows transform function dominating CPU on large datasets.
```go
// Switch from sequential to parallel
results := lop.Map(largeSlice, expensiveTransform)
```
## Testing with lo
lo helpers simplify test data generation and assertions:
```go
// Generate test fixtures
users := lo.Times(100, func(i int) User {
return User{ID: i, Name: fmt.Sprintf("user-%d", i)}
})
// Assert subset relationships
assert.True(t, lo.Every(expected, lo.Map(actual, extractID)))
// Generate random test data
ids := lo.Times(50, func(_ int) string {
return lo.RandomString(16, lo.AlphanumericCharset)
})
// Quick frequency check
counts := lo.CountValues(results)
assert.Equal(t, 3, counts["success"])
```
## Slice-to-Map Conversion
Common pattern: convert a slice into a lookup map.
```go
// By ID
userByID := lo.SliceToMap(users, func(u User) (int, User) {
return u.ID, u
})
// By key function
userByEmail := lo.KeyBy(users, func(u User) string {
return u.Email
})
// Filter + convert in one pass
activeByID := lo.FilterSliceToMap(users, func(u User) (int, User, bool) {
return u.ID, u, u.Active
})
```
references/api-reference.md
# API Reference
Complete function catalog for `samber/lo` organized by domain.
For up-to-date signatures, use `godig symbol doc github.com/samber/lo <Symbol>` (→ `samber/cc-skills-golang@golang-pkg-go-dev`) or check [pkg.go.dev/github.com/samber/lo](https://pkg.go.dev/github.com/samber/lo). Context7 is a fallback if a symbol is not indexed there.
## Slice Transformations
| Function | Description |
| --- | --- |
| `lo.Map(s, fn)` | Transform each element. `fn(item T, index int) R` |
| `lo.MapErr(s, fn)` | Map with error — stops on first error |
| `lo.UniqMap(s, fn)` | Map + deduplicate results in one pass |
| `lo.Filter(s, fn)` | Keep elements where predicate returns true |
| `lo.FilterErr(s, fn)` | Filter with error propagation |
| `lo.Reject(s, fn)` | Remove elements where predicate returns true (inverse of Filter) |
| `lo.RejectMap(s, fn)` | Reject + map in one pass |
| `lo.FilterReject(s, fn)` | Split into `(matching, non-matching)` slices |
| `lo.FlatMap(s, fn)` | Map then flatten one level |
| `lo.FlatMapErr(s, fn)` | FlatMap with error propagation |
| `lo.FilterMap(s, fn)` | Combined filter + map in one pass. `fn` returns `(R, bool)` |
| `lo.Reduce(s, fn, init)` | Fold left into accumulator |
| `lo.ReduceRight(s, fn, init)` | Fold right into accumulator |
| `lo.ForEach(s, fn)` | Iterate with side effects |
| `lo.ForEachWhile(s, fn)` | Iterate until `fn` returns false |
| `lo.Times(n, fn)` | Call `fn(index)` n times, collect results |
| `lo.Chunk(s, size)` | Split into batches of `size` |
| `lo.Window(s, size)` | Sliding window of `size` over slice |
| `lo.Sliding(s, size)` | Sliding window (overlapping), alias for Window |
| `lo.Flatten(s)` | Flatten `[][]T` → `[]T` (one level) |
| `lo.Concat(slices...)` | Concatenate multiple slices |
| `lo.Interleave(slices...)` | Interleave elements from multiple slices |
| `lo.Repeat(n, val)` | Create slice of `n` copies of `val` |
| `lo.RepeatBy(n, fn)` | Create slice of `n` values from `fn(index)` |
| `lo.Splice(s, i, elements...)` | Insert elements at index |
| `lo.Fill(s, val)` | Fill slice with value (returns new slice) |
| `lo.Reverse(s)` | Reverse order (returns new slice) |
| `lo.Shuffle(s)` | Random shuffle (returns new slice) |
| `lo.Clone(s)` | Shallow copy of slice |
### Slice-to-map conversions
| Function | Description |
| ---------------------------- | ------------------------------------------- |
| `lo.KeyBy(s, fn)` | Slice to map by key extractor. `fn(T) K` |
| `lo.Keyify(s, fn)` | Alias for KeyBy |
| `lo.SliceToMap(s, fn)` | Slice to map. `fn(T) (K, V)` |
| `lo.Associate(s, fn)` | Alias for SliceToMap |
| `lo.FilterSliceToMap(s, fn)` | Filter + slice-to-map. `fn(T) (K, V, bool)` |
| `lo.GroupBy(s, fn)` | Group into `map[K][]V` by key function |
| `lo.GroupByMap(s, fn)` | GroupBy returning `map[K]R` with transform |
### Error variants
Most transform functions have `Err` suffixes: `MapErr`, `FlatMapErr`, `FilterErr`, `ReduceErr`, `ReduceRightErr`, `ForEachErr`, `GroupByErr`, `UniqByErr`, etc. These stop processing on the first error and return `(result, error)`.
## Slice Queries
| Function | Description |
| --- | --- |
| `lo.Find(s, fn)` | First element matching predicate. Returns `(T, bool)` |
| `lo.FindOrElse(s, fallback, fn)` | First match or fallback value |
| `lo.FindIndexOf(s, fn)` | First match with index. Returns `(T, int, bool)` |
| `lo.FindLastIndexOf(s, fn)` | Last match with index |
| `lo.FindKey(m, val)` | Find key by value in map |
| `lo.FindKeyBy(m, fn)` | Find key by predicate in map |
| `lo.IndexOf(s, val)` | Index of first occurrence (-1 if not found) |
| `lo.LastIndexOf(s, val)` | Index of last occurrence |
| `lo.Contains(s, val)` | True if slice contains value. Note: prefer `slices.Contains` (stdlib Go 1.21+) |
| `lo.ContainsBy(s, fn)` | True if any element matches predicate |
| `lo.Every(s, subset)` | True if all subset elements are in s |
| `lo.EveryBy(s, fn)` | True if all elements match predicate |
| `lo.Some(s, subset)` | True if any subset element is in s |
| `lo.SomeBy(s, fn)` | True if any element matches predicate |
| `lo.None(s, subset)` | True if no subset element is in s |
| `lo.NoneBy(s, fn)` | True if no element matches predicate |
| `lo.Count(s, val)` | Count occurrences of value |
| `lo.CountBy(s, fn)` | Count elements matching predicate |
| `lo.CountValues(s)` | Frequency map `map[T]int` |
| `lo.CountValuesBy(s, fn)` | Frequency map by key function |
| `lo.Min(s)` / `lo.Max(s)` | Min/max of comparable slice |
| `lo.MinBy(s, fn)` / `lo.MaxBy(s, fn)` | Min/max by comparison function |
| `lo.MinIndex(s)` / `lo.MaxIndex(s)` | Index of min/max element |
| `lo.MinIndexBy(s, fn)` / `lo.MaxIndexBy(s, fn)` | Index of min/max by comparison |
| `lo.Earliest(vals...)` | Earliest `time.Time` value |
| `lo.EarliestBy(s, fn)` | Earliest by extractor function |
| `lo.Latest(vals...)` | Latest `time.Time` value |
| `lo.LatestBy(s, fn)` | Latest by extractor function |
| `lo.First(s)` / `lo.Last(s)` | First/last element. Returns `(T, bool)` |
| `lo.FirstOr(s, fallback)` | First element or fallback |
| `lo.FirstOrEmpty(s)` | First element or zero-value |
| `lo.LastOr(s, fallback)` | Last element or fallback |
| `lo.LastOrEmpty(s)` | Last element or zero-value |
| `lo.Nth(s, n)` | Element at index n (supports negative). Returns `(T, error)` |
| `lo.NthOr(s, n, fallback)` | Nth element or fallback |
| `lo.NthOrEmpty(s, n)` | Nth element or zero-value |
| `lo.Sample(s)` | Random element |
| `lo.SampleBy(s, fn)` | Random element matching predicate |
| `lo.Samples(s, n)` | n random elements |
| `lo.SamplesBy(s, n, fn)` | n random elements matching predicate |
| `lo.IsSorted(s)` / `lo.IsSortedBy(s, fn)` | Check if slice is sorted |
| `lo.HasPrefix(s, prefix)` | True if slice starts with prefix elements |
| `lo.HasSuffix(s, suffix)` | True if slice ends with suffix elements |
## Slice Set Operations
| Function | Description |
| --- | --- |
| `lo.Uniq(s)` | Remove duplicates (preserves first occurrence) |
| `lo.UniqBy(s, fn)` | Remove duplicates by key function |
| `lo.PartitionBy(s, fn)` | Split into groups of consecutive elements with same key |
| `lo.Compact(s)` | Remove zero-value elements |
| `lo.Without(s, vals...)` | Remove specific values |
| `lo.WithoutBy(s, fn)` | Remove elements matching predicate |
| `lo.WithoutEmpty(s)` | Remove zero-value elements (alias for Compact) |
| `lo.WithoutNth(s, indices...)` | Remove elements at specific indices |
| `lo.Union(slices...)` | Combine slices, remove duplicates |
| `lo.Intersect(a, b)` | Elements present in both slices |
| `lo.IntersectBy(a, b, fn)` | Intersection by key function |
| `lo.Difference(a, b)` | Elements in a but not in b |
| `lo.Replace(s, old, new, n)` | Replace first n occurrences |
| `lo.ReplaceAll(s, old, new)` | Replace all occurrences |
| `lo.FindDuplicates(s)` | Elements that appear more than once |
| `lo.FindDuplicatesBy(s, fn)` | Duplicates by key function |
| `lo.FindUniques(s)` | Elements that appear exactly once |
| `lo.FindUniquesBy(s, fn)` | Unique elements by key function |
| `lo.ElementsMatch(a, b)` | True if same elements regardless of order |
| `lo.ElementsMatchBy(a, b, fn)` | ElementsMatch by comparison function |
| `lo.Subset(s, offset, length)` | Sub-slice from offset with length |
| `lo.Slice(s, start, end)` | Sub-slice with bounds (safe, no panic) |
### Slice trimming
| Function | Description |
| ------------------------------- | ---------------------------------------- |
| `lo.Take(s, n)` | First n elements |
| `lo.TakeWhile(s, fn)` | Take while predicate is true |
| `lo.TakeFilter(s, n, fn)` | Take first n elements matching predicate |
| `lo.Drop(s, n)` | Skip first n elements |
| `lo.DropRight(s, n)` | Skip last n elements |
| `lo.DropWhile(s, fn)` | Drop while predicate is true |
| `lo.DropRightWhile(s, fn)` | Drop from right while true |
| `lo.DropByIndex(s, indices...)` | Drop elements at specific indices |
| `lo.Cut(s, start, end)` | Remove elements between start and end |
| `lo.CutPrefix(s, prefix)` | Remove prefix from slice |
| `lo.CutSuffix(s, suffix)` | Remove suffix from slice |
| `lo.Trim(s, fn)` | Trim both ends while predicate is true |
| `lo.TrimLeft(s, fn)` | Trim left while predicate is true |
| `lo.TrimRight(s, fn)` | Trim right while predicate is true |
| `lo.TrimPrefix(s, prefix)` | Remove exact prefix elements |
| `lo.TrimSuffix(s, suffix)` | Remove exact suffix elements |
## Map Operations
| Function | Description |
| --- | --- |
| `lo.Keys(m)` | All keys as a slice. Note: for Go 1.23+, prefer `slices.Collect(maps.Keys(m))` when stdlib coverage is enough |
| `lo.UniqKeys(m)` | Unique keys (useful for multi-maps) |
| `lo.Values(m)` | All values |
| `lo.UniqValues(m)` | Unique values |
| `lo.HasKey(m, key)` | True if key exists |
| `lo.ValueOr(m, key, fallback)` | Value or fallback if key missing |
| `lo.PickBy(m, fn)` | Keep entries where predicate is true |
| `lo.PickByKeys(m, keys)` | Keep only specified keys |
| `lo.PickByValues(m, vals)` | Keep only specified values |
| `lo.OmitBy(m, fn)` | Remove entries where predicate is true |
| `lo.OmitByKeys(m, keys)` | Remove specified keys |
| `lo.OmitByValues(m, vals)` | Remove specified values |
| `lo.FilterKeys(m, fn)` | Keep entries where key matches predicate |
| `lo.FilterValues(m, fn)` | Keep entries where value matches predicate |
| `lo.MapKeys(m, fn)` | Transform keys |
| `lo.MapValues(m, fn)` | Transform values |
| `lo.MapEntries(m, fn)` | Transform both key and value |
| `lo.MapToSlice(m, fn)` | Convert map entries to slice |
| `lo.FilterMapToSlice(m, fn)` | Filter + map-to-slice in one pass |
| `lo.Entries(m)` / `lo.ToPairs(m)` | Map → `[]lo.Entry[K,V]` |
| `lo.FromEntries(entries)` / `lo.FromPairs(pairs)` | `[]lo.Entry` → map |
| `lo.Invert(m)` | Swap keys and values |
| `lo.Assign(maps...)` | Merge maps (last wins) |
| `lo.ChunkEntries(m, size)` | Split map into chunks of `size` entries |
## String Operations
| Function | Description |
| --------------------------------- | --------------------------------- |
| `lo.Substring(s, offset, length)` | Safe substring (rune-aware) |
| `lo.ChunkString(s, size)` | Split string into chunks |
| `lo.RuneLength(s)` | Count runes (not bytes) |
| `lo.PascalCase(s)` | `"hello world"` → `"HelloWorld"` |
| `lo.CamelCase(s)` | `"hello world"` → `"helloWorld"` |
| `lo.KebabCase(s)` | `"hello world"` → `"hello-world"` |
| `lo.SnakeCase(s)` | `"hello world"` → `"hello_world"` |
| `lo.Words(s)` | Split into words |
| `lo.Capitalize(s)` | Capitalize first letter |
| `lo.Ellipsis(s, maxLen)` | Truncate with `…` |
| `lo.RandomString(n, charset)` | Generate random string |
## Math & Comparison
| Function | Description |
| --------------------------------------- | ---------------------------------- |
| `lo.Range(n)` | `[0, 1, ..., n-1]` |
| `lo.RangeFrom(start, n)` | `[start, start+1, ..., start+n-1]` |
| `lo.RangeWithSteps(start, end, step)` | Custom step range |
| `lo.Clamp(val, min, max)` | Constrain value to range |
| `lo.Sum(s)` | Sum of numeric slice |
| `lo.SumBy(s, fn)` | Sum by extractor function |
| `lo.Product(s)` / `lo.ProductBy(s, fn)` | Product of elements |
| `lo.Mean(s)` / `lo.MeanBy(s, fn)` | Arithmetic mean |
| `lo.Mode(s)` | Most frequent element(s) |
### Conditionals
| Function | Description |
| --- | --- |
| `lo.Ternary(cond, a, b)` | Inline if/else (both values evaluated) |
| `lo.TernaryF(cond, fnA, fnB)` | Lazy ternary (only winning branch evaluated) |
| `lo.If(cond, val).ElseIf(cond2, val2).Else(val3)` | Chained conditional |
| `lo.IfF(cond, fn).ElseIfF(cond2, fn2).ElseF(fn3)` | Chained conditional with lazy evaluation |
| `lo.Switch[R](val).Case(v1, r1).Case(v2, r2).Default(r3)` | Pattern matching |
## Tuples
| Function | Description |
| --- | --- |
| `lo.T2(a, b)` ... `lo.T9(...)` | Create tuple from values |
| `lo.Unpack2(t)` ... `lo.Unpack9(t)` | Destructure tuple into values |
| `lo.Zip2(a, b)` ... `lo.Zip9(...)` | Pair elements from multiple slices |
| `lo.ZipBy2(a, b, fn)` ... `lo.ZipBy9(...)` | Zip with custom merge function |
| `lo.Unzip2(pairs)` ... `lo.Unzip9(...)` | Split pairs back into slices |
| `lo.UnzipBy2(s, fn)` ... `lo.UnzipBy9(...)` | Unzip with custom split function |
| `lo.CrossJoin2(a, b)` ... `lo.CrossJoin9(...)` | Cartesian product of slices |
| `lo.CrossJoinBy2(a, b, fn)` ... `lo.CrossJoinBy9(...)` | Cartesian product with transform |
## Channel Operations
| Function | Description |
| --- | --- |
| `lo.ChannelDispatcher(ch, count, strategy)` | Fan-out to multiple channels. Strategies: `RoundRobin`, `Random`, `WeightedRandom`, `First`, `Least`, `Most` |
| `lo.SliceToChannel(bufSize, s)` | Convert slice to buffered channel |
| `lo.ChannelToSlice(ch)` | Collect channel into slice |
| `lo.Generator(bufSize, fn)` | Create channel from generator function |
| `lo.Buffer(ch, size)` | Buffer channel output |
| `lo.BufferWithContext(ctx, ch, size)` | Buffer with context cancellation |
| `lo.BufferWithTimeout(ch, size, timeout)` | Buffer with timeout |
| `lo.FanIn(channels...)` | Merge multiple channels into one |
| `lo.FanOut(ch, count)` | Duplicate channel to multiple consumers |
## Concurrency Helpers
| Function | Description |
| --- | --- |
| `lo.Async(fn)` | Run function in goroutine, return channel for result |
| `lo.Async0` ... `lo.Async6` | Async with tuple returns |
| `lo.Attempt(maxRetries, fn)` | Retry until success or max retries |
| `lo.AttemptWithDelay(max, delay, fn)` | Retry with fixed delay between attempts |
| `lo.AttemptWhile(fn)` | Retry while predicate returns true |
| `lo.AttemptWhileWithDelay(delay, fn)` | AttemptWhile with delay between attempts |
| `lo.Debounce(duration, fn)` | Debounce — execute after quiet period. Returns `(func(), func())` (trigger, cancel) |
| `lo.DebounceBy(duration, fn)` | Debounce by key — separate debounce per key |
| `lo.Throttle(duration, fn)` | Throttle — max one execution per duration |
| `lo.ThrottleWithCount(duration, count, fn)` | Throttle allowing N executions per duration |
| `lo.ThrottleBy(duration, fn)` | Throttle by key — separate throttle per key |
| `lo.ThrottleByWithCount(duration, count, fn)` | ThrottleBy with count |
| `lo.WaitFor(fn, timeout, heartbeat)` | Poll until condition met or timeout |
| `lo.WaitForWithContext(ctx, fn, ...)` | WaitFor with context cancellation |
| `lo.Synchronize(mutexes...)` | Create synchronized wrapper. `sync.Locker`-based |
| `lo.Transaction(fn)` | Execute function with rollback on error |
## Type Manipulation
| Function | Description |
| --- | --- |
| `lo.ToPtr(v)` | Value to pointer (`&v`) |
| `lo.Nil[T]()` | Typed nil pointer |
| `lo.EmptyableToPtr(v)` | Value to pointer, zero-value becomes nil |
| `lo.FromPtr(p)` | Pointer to value (zero-value if nil) |
| `lo.FromPtrOr(p, fallback)` | Pointer to value with fallback |
| `lo.ToSlicePtr(s)` | `[]T` → `[]*T` |
| `lo.FromSlicePtr(s)` | `[]*T` → `[]T` (nil becomes zero-value) |
| `lo.FromSlicePtrOr(s, fallback)` | `[]*T` → `[]T` with fallback for nil |
| `lo.ToAnySlice(s)` | `[]T` → `[]any` |
| `lo.FromAnySlice[T](s)` | `[]any` → `([]T, bool)` |
| `lo.IsNil(v)` | Nil-safe check (handles interface nil) |
| `lo.IsNotNil(v)` | Inverse of IsNil |
| `lo.Empty[T]()` | Zero-value of type T |
| `lo.IsEmpty(v)` | True if zero-value |
| `lo.IsNotEmpty(v)` | True if not zero-value |
| `lo.Coalesce(vals...)` | First non-zero value |
| `lo.CoalesceOrEmpty(vals...)` | First non-zero or zero-value |
| `lo.CoalesceSlice(slices...)` | First non-empty slice |
| `lo.CoalesceSliceOrEmpty(slices...)` | First non-empty slice or empty |
| `lo.CoalesceMap(maps...)` | First non-empty map |
| `lo.CoalesceMapOrEmpty(maps...)` | First non-empty map or empty |
## Function Helpers
| Function | Description |
| --- | --- |
| `lo.Partial(fn, arg)` | Partial application — bind first argument |
| `lo.Partial2(fn, arg)` ... `lo.Partial5(fn, arg)` | Partial with 2-5 args |
## Duration Helpers
| Function | Description |
| --- | --- |
| `lo.Duration(fn)` | Measure execution time. Returns `time.Duration` |
| `lo.Duration0(fn)` ... `lo.Duration10(fn)` | Duration with 0-10 return values — returns `(time.Duration, ...)` |
## Error Helpers
| Function | Description |
| --- | --- |
| `lo.Must(val, err)` | Panic if err != nil, return val. Use in tests/init only |
| `lo.Must0(err)` ... `lo.Must6(...)` | Must with 0-6 return values |
| `lo.Try(fn)` | Run fn, return true if no panic |
| `lo.Try1(fn)` ... `lo.Try6(fn)` | Try with 1-6 return values |
| `lo.TryOr(fn, fallback)` | Run fn, return fallback on panic |
| `lo.TryOr1(fn, fallback)` ... `lo.TryOr6(...)` | TryOr with 1-6 return values |
| `lo.TryCatch(fn, catchFn)` | Try with catch handler |
| `lo.TryWithErrorValue(fn)` | Try returning recovered error value |
| `lo.TryCatchWithErrorValue(fn, catchFn)` | TryCatch with error value |
| `lo.Validate(conditions...)` | Return first error from condition list |
| `lo.ErrorsAs[T](err)` | Generic wrapper for `errors.As` |
| `lo.Assert[T](v)` | Type assertion with panic message |
| `lo.Assertf[T](v, format, args...)` | Type assertion with formatted panic message |
references/package-guide.md
# Package Guide
samber/lo ships five packages. Each serves a different performance/ergonomics trade-off.
## Import Paths and Aliases
```go
import (
"github.com/samber/lo" // lo — core, immutable
"github.com/samber/lo/parallel" // lop — concurrent transforms
"github.com/samber/lo/mutable" // lom — in-place mutations
"github.com/samber/lo/it" // loi — lazy iterators (Go 1.23+)
"github.com/samber/lo/exp/simd" // experimental SIMD
)
```
## `lo` — Core (Immutable)
The default package. 300+ functions that return new collections without modifying inputs.
**Mental model:** Functional transforms like JavaScript's `Array.prototype.map/filter/reduce`, but type-safe via generics.
**Characteristics:**
- Every function allocates a new result slice/map — the input is never modified
- Safe for concurrent reads on the input while transforms run
- Composable: output of one function feeds directly into the next
**Use when:** Always start here. Only move to other packages when profiling reveals a measured bottleneck.
```go
// Immutable — users slice is untouched
active := lo.Filter(users, func(u User, _ int) bool {
return u.Active
})
```
## `lo/parallel` (lop) — Concurrent Transforms
Parallel variants of core functions. Each element is processed in a separate goroutine with automatic worker pooling.
**Available functions:** `Map`, `ForEach`, `Times`, `GroupBy`, `PartitionBy`
**Characteristics:**
- Results preserve original order despite concurrent execution
- Internal goroutine pool manages concurrency (not configurable via API — one goroutine per element)
- Synchronization via `sync.WaitGroup`
**Use when:**
- CPU-bound transforms on large datasets (~1000+ items)
- Transform function is expensive (parsing, hashing, computing)
- Order must be preserved
**Do NOT use when:**
- Small datasets (<100 items) — goroutine creation overhead exceeds benefit
- I/O-bound work (HTTP calls, DB queries) — use `errgroup` with context cancellation instead
- Transform function is trivial (field access, type cast) — `lo.Map` is faster
```go
// CPU-heavy: parse 10k JSON documents in parallel
parsed := lop.Map(rawDocs, func(doc []byte, _ int) *Document {
return parseDocument(doc) // expensive operation
})
```
**Diagnose:** `go tool pprof -cpu` — if transform function dominates CPU profile and dataset is large, `lop` helps.
## `lo/mutable` (lom) — In-Place Mutations
Modify the original slice directly. Zero allocation overhead.
**Available functions:** `Filter`, `Map`, `Shuffle`, `Reverse`, `Replace`
**Characteristics:**
- Modifies the input slice — callers must expect side effects
- `lom.Filter` shortens the slice (removes non-matching elements in-place)
- `lom.Map` transforms elements in-place (preserves length)
- Uses Fisher-Yates for `Shuffle`
- Not safe for concurrent access to the source slice
**Use when:**
- `go tool pprof -alloc_objects` confirms allocation pressure from `lo.Filter`/`lo.Map`
- Working with very large slices where GC pressure is measurable
- You explicitly want to modify the source and won't need the original
**Do NOT use when:**
- Multiple goroutines read the same slice
- You need the original data after the transform
- Code readability matters more than micro-optimization
```go
// In-place filter — modifies 'items' directly
items = lom.Filter(items, func(item Item, _ int) bool {
return item.Price > 0
})
```
**Diagnose:** 1- `go tool pprof -alloc_objects` — find which `lo.*` calls allocate the most 2- `go build -gcflags="-m"` — check if result slices escape to heap
## `lo/it` (loi) — Lazy Iterators
Go 1.23+ iterator support with lazy evaluation. Transforms are deferred until consumed — no intermediate slices allocated.
**Characteristics:**
- Uses `range`-over-func (Go 1.23+)
- Composable pipelines: `loi.Map` → `loi.Filter` → `loi.Take` runs as a single pass
- No intermediate slice allocations between pipeline stages
- Modules: `channel`, `find`, `intersect`, `map`, `math`, `seq`, `string`, `tuples`, `type_manipulation`
**Use when:**
- Chaining 3+ transforms on large datasets — eliminates intermediate allocations
- Processing sequences where you only need a subset (lazy `Take`/`TakeWhile`)
- Building composable pipelines with range-over-func
**Do NOT use when:**
- Go version < 1.23
- Simple single-step transforms — `lo.Map` is clearer and has negligible overhead
- You need random access to intermediate results
```go
// Lazy pipeline — no intermediate slices allocated
for name := range loi.Map(
loi.Filter(users, func(u User) bool { return u.Active }),
func(u User) string { return u.Name },
) {
fmt.Println(name)
}
```
## `lo/exp/simd` — Experimental SIMD
SIMD (Single Instruction Multiple Data) optimized operations for numeric types on amd64.
**Use when:** Bulk numeric operations after benchmarking confirms the bottleneck. Very specialized.
**Warning:** This package is experimental. API may break between minor versions. Not covered by semver stability guarantees. Do not use in production without version pinning.
## Decision Flowchart
```
Start with lo.Map/Filter/Reduce (immutable, safe)
│
├─ Profiler shows allocation pressure?
│ └─ Yes → Switch specific calls to lom (mutable)
│
├─ Profiler shows CPU-bound transform is slow?
│ └─ Yes + large dataset → Switch to lop (parallel)
│
├─ Chaining 3+ transforms with intermediate allocations?
│ └─ Yes + Go 1.23+ → Switch to loi (lazy iterators)
│
└─ Need reactive/streaming over infinite events?
└─ Yes → Use samber/ro instead (different library)
```
## Comparison Table
| Aspect | `lo` | `lop` | `lom` | `loi` | `simd` |
| --- | --- | --- | --- | --- | --- |
| Allocations | New slice/map | New slice/map | Zero (in-place) | Zero (lazy) | Varies |
| Goroutines | None | 1 per element | None | None | None |
| Order preserved | Yes | Yes | Yes | Yes | Yes |
| Input modified | No | No | Yes | No | Varies |
| Concurrent-safe | Read-safe | Read-safe | Not safe | Read-safe | Varies |
| API stability | Stable | Stable | Stable | Stable | Experimental |
| Go version | 1.18+ | 1.18+ | 1.18+ | 1.23+ | 1.25+ |