# 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+ |