Awesome Go

glinq

CategoryData Structures and Algorithms
SubcategoryIterators
Stars18

LINQ-like lazy evaluation library with type-safe generics, performance optimizations and zero dependencies

About glinq

Go Report Card GoDoc codecov

glinq

LINQ-like API for Go with support for lazy evaluation.

Features

  • 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 Enumerable interface
  • Performance Optimized: Automatic size tracking enables O(1) Count() and Any(), preallocation in ToSlice()

Installation

go get github.com/CreateLab/glinq

Quick Start

package 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]
}

Basic Examples

Filtering and Transformation

// 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

Sorting and Ordering

// 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]

Removing Duplicates

// 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()

Set Operations

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]

Performance Characteristics

glinq is optimized for performance with zero-copy defaults and automatic size tracking, following C# LINQ's approach.

Size-Based Optimizations

glinq automatically tracks size information and uses it for optimizations:

  • Count() - O(1) when size is known, O(n) otherwise
  • Any() - O(1) when size is known, iterates until first element otherwise
  • ToSlice() - Preallocates capacity when size is known, avoiding reallocations
  • Chunk() - 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.).

Stream Creation Performance

From() - Zero-Copy (O(1))

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() - Defensive Copy (O(n))

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 stream

Characteristics:

  • 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() - Keys Only Copy (O(n) keys, O(1) values)

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-demand

Characteristics:

  • 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() - Full Snapshot (O(n))

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 stream

Characteristics:

  • 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

Benchmark Comparison

Run benchmarks to see the performance difference:

go test -bench=BenchmarkFrom -benchmem ./pkg/glinq/...

Expected Results:

  • From(): Constant time regardless of slice size
  • FromSafe(): Linear time scaling with slice size
  • FromMap(): Faster than FromMapSafe() for large maps with expensive value types

Recommendations

  1. Use From() by default - Maximum performance, safe in most cases
  2. Use FromSafe() - Only when you explicitly need isolation
  3. Use FromMap() - Default for maps, especially with large or expensive value types
  4. Use FromMapSafe() - Only when you need complete map isolation

Documentation

📚 Full Documentation & Wiki - Complete API reference, examples, and guides

Quick Links

Requirements

  • Go 1.18+ (for generics support)

Comparison with Similar Libraries

Featureglinqsamber/lothoas/go-funk
EvaluationLazy (deferred)Eager (immediate)Eager (immediate)
API StyleFluent/ChainableFunctionalFunctional
Type SafetyFull (generics)Full (generics)Runtime (reflection)
PerformanceSingle pass, no intermediate arraysCreates intermediate arraysSlower due to reflection
Memory UsageMinimal (lazy)Higher (eager)Higher (eager + reflection)
ExtensibilityInterface-based (Enumerable/Stream)NoneNone
DependenciesZeroZeroZero

Performance Benchmarks

TL;DR

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.

Quick Comparison

Scenarioglinqsamber/logo-linqgo-funk
Filter+Map+First (1M items)✅ 0.4μs1.4ms1.8μs337ms
Complex Chain (1M items)✅ 0.6μs2.5ms0.9μs478ms
Memory Usage✅ 280B16MB992B188MB
Complex Chain Memory✅ 600B24MB552B214MB

Library Recommendations

🟢 glinq

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)

Testing

go test ./...

Running Examples

go run examples/basic/main.go

License

MIT License - see LICENSE file for details.

Frequently Asked Questions

What is glinq?

glinq is a Data Structures and Algorithms library for the Go programming language. LINQ-like lazy evaluation library with type-safe generics, performance optimizations and zero dependencies

How do I install glinq?

Install glinq with the Go module system using `go get CreateLab/glinq`. Check the repository for the current installation instructions.

What category does glinq belong to?

glinq is listed under Data Structures and Algorithms, specifically Iterators.

← Back to Iterators