# go-linq [](https://godoc.org/github.com/ahmetb/go-linq/v5) [](https://github.com/ahmetb/go-linq/actions/workflows/ci.yml) [](https://coveralls.io/github/ahmetb/go-linq?branch=master) [](https://goreportcard.com/report/github.com/ahmetb/go-linq)
A powerful language integrated query (LINQ) library for Go.
* **Fully type-safe:** `Query[T]` is generic, and type-changing operators like
`Select`, `Join` and `GroupBy` are *generic methods* (a Go 1.27 language
feature) — no `interface{}`/`any`, no type assertions, no reflection.
* **Fast:** 5–15× faster than go-linq v4 and allocation-free per element
(see [Performance](#performance)).
* Written in vanilla Go, no dependencies!
* Complete lazy evaluation with iterator pattern based on the standard
`iter.Seq[T]` type.
* Safe for concurrent use.
* Supports slices, maps, strings, channels, `iter.Seq[T]` iterators, and
custom collections.
> [!NOTE]
> **Why was go-linq rewritten for Go 1.27?** Go now supports
> [generic methods](https://go.dev/issue/77273) — methods that declare their
> own type parameters, like `func (q Query[T]) Select[TResult any](...)`.
> This is the one language feature LINQ-style chaining was missing: without
> it, a method could not return a query of a *different* element type, which
> forced v1–v4 to erase everything to `any` and patch over it with runtime
> reflection. With it, the element type flows through the entire chain at
> compile time.
The same query, before and after:
| go-linq v4 — type-erased |
go-linq v5 — typed |
|
```go
var owners []string
FromSlice(cars).Where(func(c any) bool {
return c.(Car).year >= 2015
}).Select(func(c any) any {
return c.(Car).owner
}).ToSlice(&owners)
```
|
```go
owners := FromSlice(cars).Where(func(c Car) bool {
return c.year >= 2015
}).Select(func(c Car) string {
return c.owner
}).ToSlice()
```
|
## Requirements
go-linq v5 requires **Go 1.27** (currently in release candidate), because it
relies on [generic methods](https://go.dev/issue/77273) for type-changing
operators. With Go 1.26 or newer installed, the toolchain listed in `go.mod`
is downloaded automatically.
For older Go versions, use [go-linq v4](https://github.com/ahmetb/go-linq/tree/master),
which offers the same operators with an `any`-based API.
## Installation
go get github.com/ahmetb/go-linq/v5
## Quickstart
Usage is as easy as chaining methods like:
`FromSlice(slice)` `.Where(predicate)` `.Select(selector)` `.Union(data)`
Type parameters are fully inferred from your functions: you never write
explicit type arguments in a query chain.
**Example 1: Find the author who has written the most books**
```go
import . "github.com/ahmetb/go-linq/v5"
type Book struct {
id int
title string
authors []string
}
author, ok := FromSlice(books).SelectMany( // make a flat sequence of authors
func(book Book) Query[string] {
return FromSlice(book.authors)
}).GroupBy( // group by author
func(author string) string { return author },
func(author string) string { return author },
).MaxBy( // take the largest group
func(group Group[string, string]) int {
return len(group.Group)
})
// author.Key is the author with the most books
```
**Example 2: Implement a custom method that leaves only values greater than the specified threshold**
```go
type MyQuery Query[int]
func (q MyQuery) GreaterThan(threshold int) Query[int] {
return Query[int](q).Where(func(item int) bool {
return item > threshold
})
}
result := MyQuery(Range(1, 10)).GreaterThan(5).ToSlice()
```
**Example 3: "MapReduce" in a slice of string sentences to list the top 5 most used words**
```
…
```
## Manual Iteration
The `Query[T]` type exposes an `Iterate` field of type `iter.Seq[T]`, which
integrates with Go's native iteration style and the `iter`/`slices` standard
library packages.
**Example 4: Iterate over a query using the standard `for ... range` loop**
```go
q := FromSlice([]int{1, 2, 3, 4})
for v := range q.Iterate {
fmt.Println(v)
}
```
## Data Source Constructors
Each constructor is typed for its specific input, and the element type of the
resulting query is inferred from the argument:
- `FromSlice` — creates a query from a slice.
- `FromMap` — creates a `Query[KeyValue[TKey, TValue]]` from a map.
- `FromChannel` — creates a query from a channel.
- `FromChannelWithContext` — creates a query from a channel with `Context` support.
- `FromString` — creates a `Query[rune]` from a string.
- `FromSeq` — creates a query from any standard `iter.Seq[T]` iterator,
including custom collections that expose an iterator method.
- `Range`, `Repeat` — generate sequences.
The runtime-reflection based `From(any)` constructor from v4 has been removed:
in a fully-typed API the element type must be known at the call site.
## Performance
v5 eliminates the three taxes the type-erased v4 API paid on every element:
interface boxing, type assertions, and reflection. Per-element work in a v5
chain is just typed closure calls; the only allocations are the fixed closure
captures made when the query is constructed.
Measured on Apple M5 Pro with go1.27, 1M-element `[]int` (100k structs for
the projection case):
| Benchmark | v4 (`any` API) | v4 (`…T` reflection API) | **v5** | hand-written loop |
|---|---|---|---|---|
| `Where` → `Sum` | 23.0 ms / 999,754 allocs | 103.9 ms / 3.0M allocs | **1.5 ms / 3 allocs** | 0.45 ms / 0 allocs |
| `Where` → `ToSlice` | 16.4 ms / 1.5M allocs | — | **2.5 ms / 38 allocs** | (result slice only) |
| struct `Where` → `Select` → `ToSlice` (100k) | 3.33 ms / 226,685 allocs | 18.0 ms / 490k allocs | **0.71 ms / 30 allocs** | 26 allocs |
In short: **5–15× faster** than idiomatic v4, **25–77× faster** than the v4
`…T` reflection API, with allocations dropping from *O(n)* to *O(1)* per
query. The residual gap to a hand-written loop is the per-element closure
call, inherent to any lazy iterator.
## Migrating from v4
See [MIGRATION.md](MIGRATION.md) for a complete v4 → v5 symbol table. The
highlights:
* `Query` is now `Query[T]`; all operators take typed functions
(`func(T) bool` instead of `func(any) bool`).
* All `…T` reflection twins (`WhereT`, `SelectT`, …) are gone — the base
methods are now just as clean and much faster.
* Element-returning terminals (`First`, `Last`, `Single`, `Aggregate`,
`Min`, `Max`, …) return `(T, bool)` instead of a nil-able `any`.
* `Min`, `Max`, `Sum`, `Average`, `ToMap` are package-level functions
(their constraints depend on the element type); chainable `MinBy`,
`MaxBy`, `SumBy`, `AverageBy`, `ToMapBy` methods are available.
* `ToSlice()` returns `[]T` instead of filling a pointer argument.
## Release Notes
```
…
```