LINQ-like API for Go with support for lazy evaluation.
- Lazy Evaluation: All intermediate operations are executed only when the result is materialized
- Type Safe: Full support for generics (Go 1.18+)
- Composable: Operations can be easily combined into chains
- Zero Dependencies: No external dependencies required
- Extensible: Works with any type implementing
Enumerableinterface - Performance Optimized: Automatic size tracking enables O(1)
Count()andAny(), preallocation inToSlice()
go get github.com/CreateLab/glinqpackage main
import (
"fmt"
"github.com/CreateLab/glinq/pkg/glinq"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := glinq.From(numbers).
Where(func(x int) bool { return x > 5 }).
Select(func(x int) int { return x * 2 }).
ToSlice()
fmt.Println(result) // [12, 14, 16, 18, 20]
}// Filter even numbers
evens := glinq.From([]int{1, 2, 3, 4, 5}).
Where(func(x int) bool { return x%2 == 0 }).
ToSlice()
// [2, 4]
// Transform to strings
strings := glinq.Select(
glinq.From([]int{1, 2, 3}),
func(x int) string { return fmt.Sprintf("num_%d", x) },
).ToSlice()
// []string{"num_1", "num_2", "num_3"}
// Check if stream has elements (O(1) when size is known)
hasElements := glinq.From([]int{1, 2, 3}).Any()
// true
// Check if any element matches predicate
hasEven := glinq.From([]int{1, 2, 3}).AnyMatch(func(x int) bool { return x%2 == 0 })
// true// Sort ascending
sorted := glinq.From([]int{5, 2, 8, 1, 9}).
OrderBy(func(a, b int) int { return a - b }).
ToSlice()
// [1, 2, 5, 8, 9]
// Get top 3 smallest
top3 := glinq.From([]int{5, 2, 8, 1, 9, 3}).
TakeOrdered(3).
ToSlice()
// [1, 2, 3]// Remove duplicates
unique := glinq.Distinct(glinq.From([]int{1, 2, 2, 3, 3, 4})).ToSlice()
// [1, 2, 3, 4]
// Remove duplicates by key
type Person struct { ID int; Name string }
people := []Person{{1, "Alice"}, {1, "Alice2"}, {2, "Bob"}}
uniquePeople := glinq.From(people).
DistinctBy(func(p Person) any { return p.ID }).
ToSlice()set1 := glinq.From([]int{1, 2, 3})
set2 := glinq.From([]int{3, 4, 5})
union := glinq.Union(set1, set2).ToSlice() // [1, 2, 3, 4, 5]
intersect := glinq.Intersect(set1, set2).ToSlice() // [3]
except := glinq.Except(set1, set2).ToSlice() // [1, 2]glinq is optimized for performance with zero-copy defaults and automatic size tracking, following C# LINQ's approach.
glinq automatically tracks size information and uses it for optimizations:
Count()- O(1) when size is known, O(n) otherwiseAny()- O(1) when size is known, iterates until first element otherwiseToSlice()- Preallocates capacity when size is known, avoiding reallocationsChunk()- Preallocates result slice capacity when size is known
Size is preserved through 1-to-1 transformations (Select, Take, Skip, etc.) and lost through filtering operations (Where, DistinctBy, etc.).
From() creates a stream instantly without copying data - it holds a reference to the original slice:
data := []int{1, 2, 3, /* ...million elements */ }
stream := From(data) // O(1) - instant, no copying!Characteristics:
- Time Complexity: O(1) - constant time creation
- Space Complexity: O(1) - no additional memory allocation
- Behavior: Modifications to the original slice are visible during iteration
- Use Case: Default choice for maximum performance (safe in 99% of cases)
FromSafe() creates an isolated snapshot by copying the entire slice:
data := []int{1, 2, 3, 4, 5}
stream := FromSafe(data) // O(n) - copies all elements
data[0] = 999 // Won't affect streamCharacteristics:
- Time Complexity: O(n) - linear time for copying
- Space Complexity: O(n) - full slice copy in memory
- Behavior: Completely isolated from original slice modifications
- Use Case: When you need protection from concurrent modifications or want isolation
FromMap() copies only keys and reads values on-demand from the map:
m := map[string]int{"a": 1, "b": 2, /* ...thousands of entries */ }
stream := FromMap(m) // Copies keys only, reads values on-demandCharacteristics:
- Time Complexity: O(n) for key copying, O(1) per value read
- Space Complexity: O(n) for keys only (not values)
- Behavior: Values are read from map during iteration (modifications visible)
- Use Case: Optimal for large maps with expensive-to-copy value types
FromMapSafe() creates a complete snapshot of all key-value pairs:
m := map[string]int{"a": 1, "b": 2}
stream := FromMapSafe(m) // Full snapshot
m["a"] = 999 // Won't affect streamCharacteristics:
- Time Complexity: O(n) - copies all key-value pairs
- Space Complexity: O(n) - full map snapshot in memory
- Behavior: Completely isolated from original map modifications
- Use Case: When you need complete isolation from map changes
Run benchmarks to see the performance difference:
go test -bench=BenchmarkFrom -benchmem ./pkg/glinq/...Expected Results:
From(): Constant time regardless of slice sizeFromSafe(): Linear time scaling with slice sizeFromMap(): Faster thanFromMapSafe()for large maps with expensive value types
- Use
From()by default - Maximum performance, safe in most cases - Use
FromSafe()- Only when you explicitly need isolation - Use
FromMap()- Default for maps, especially with large or expensive value types - Use
FromMapSafe()- Only when you need complete map isolation
📚 Full Documentation & Wiki - Complete API reference, examples, and guides
- Go 1.18+ (for generics support)
| Feature | glinq | samber/lo | thoas/go-funk |
|---|---|---|---|
| Evaluation | Lazy (deferred) | Eager (immediate) | Eager (immediate) |
| API Style | Fluent/Chainable | Functional | Functional |
| Type Safety | Full (generics) | Full (generics) | Runtime (reflection) |
| Performance | Single pass, no intermediate arrays | Creates intermediate arrays | Slower due to reflection |
| Memory Usage | Minimal (lazy) | Higher (eager) | Higher (eager + reflection) |
| Extensibility | Interface-based (Enumerable/Stream) | None | None |
| Dependencies | Zero | Zero | Zero |
glinq is 10-1000x faster than go-funk, 2-5x faster than go-linq, with 10x better memory efficiency than samber/lo for complex chains.
| Scenario | glinq | samber/lo | go-linq | go-funk |
|---|---|---|---|---|
| Filter+Map+First (1M items) | ✅ 0.4μs | 1.4ms | 1.8μs | 337ms |
| Complex Chain (1M items) | ✅ 0.6μs | 2.5ms | 0.9μs | 478ms |
| Memory Usage | ✅ 280B | 16MB | 992B | 188MB |
| Complex Chain Memory | ✅ 600B | 24MB | 552B | 214MB |
Best for: Complex query chains, large datasets, memory-sensitive applications
Strengths: 10x less memory than samber/lo in chains, minimal allocations, lazy evaluation
Example: From(data).Where(...).Select(...).Take(10)
go test ./...go run examples/basic/main.goMIT License - see LICENSE file for details.