go-brrr - Brotli compression for Go
June 17, 2026 · View on GitHub

go-brrr - Brotli compression for Go
Brotli compression library for Go (RFC 7932), with encoder and decoder support.
Highlights
- No C toolchain. Builds with standard Go tooling.
- Faster than other pure-Go brotli libraries at every quality level we measure (see Benchmarks).
- Even faster than CGO brotli on levels 2-9.
- Compound dictionaries.
- Encoder tuning.
LGWin(window size) andSizeHint(expected total input size) are exposed viaWriterOptions.SizeHintlets the encoder pick context modeling and hasher parameters tuned for the actual payload size.
Status
The encoder and decoder are covered by compatibility tests and fuzzing. The public API is stable and follows semantic versioning.
Compatibility
go-brrr implements Brotli RFC 7932 and is tested against the Brotli reference corpus. Encoded output is byte-compatible with the C reference implementation.
Compared to other Go brotli libraries
| go-brrr | andybalholm | google/brotli/go/brotli | cbrotli | |
|---|---|---|---|---|
| Pure Go (no cgo) | ✓ | ✓ | ✓ | ✗ |
| Encoder | ✓ | ✓ | ✗ | ✓ |
| Decoder | ✓ | ✓ | ✓ | ✓ |
| Compound dictionaries (encode) | ✓ | ✗ | n/a | ✓ |
| Compound dictionaries (decode) | ✓ | ✗ | ✓ | ✓ |
LGWin tuning | ✓ | ✓ | n/a | ✓ |
SizeHint | ✓ | ✗ | n/a | ✗ |
Writer Reset | ✓ | ✓ | n/a | ✗ |
Reader Reset | ✓ | ✓ | ✗ | ✗ |
If you're using andybalholm/brotli, go-brrr is a near drop-in upgrade with higher throughput on both compression and decompression, plus compound-dictionary and SizeHint support. If you're using cbrotli, go-brrr gives up cgo, adds multi-chunk compound dictionaries, and exposes poolable Writer/Reader instances. cbrotli has no Reset, so each stream allocates a fresh encoder/decoder state, which is noticeable on many-small-file workloads.
Install
go get github.com/molecule-man/go-brrr
import "github.com/molecule-man/go-brrr"
The import path is github.com/molecule-man/go-brrr; the package name is brrr.
Examples
Compression
func Example_compress() {
input := []byte("Hello, brotli!")
var compressed bytes.Buffer
w, err := brrr.NewWriter(&compressed, 6)
if err != nil {
log.Fatal(err)
}
if _, err := w.Write(input); err != nil {
log.Fatal(err)
}
if err := w.Close(); err != nil {
log.Fatal(err)
}
}
More examples are available in example_test.go and the Go package docs: round-trip compression and decompression, one-shot decompression, reusing writers and readers, pooling, and compound dictionaries.
When to use go-brrr
The best use case for brotli is static asset compression - CSS, JS, HTML, fonts, WASM - where you compress once at build time and serve the result millions of times. Use quality 11 for this: speed doesn't matter because you pay the cost once, and brotli q11 delivers ratios that neither gzip nor zstd can match. Every browser shipped since 2016 supports Content-Encoding: br.
For on-the-fly compression, brotli q5–6 is a strong choice if you're already using zstd at its highest level: q5 is often faster with a better ratio, and q6 is only slightly slower with an even better ratio. At lower compression levels, zstd is significantly faster - if throughput is your priority and you don't need the best ratio, zstd is the better tool for the job.
If you compress or decompress repeatedly (e.g. per request in a webserver), keep *brrr.Writer and *brrr.Reader instances in sync.Pools and Reset each one into the next stream rather than allocating new instances each time. See the compiled examples.
Implementation notes
go-brrr is optimized for throughput. Some hot paths intentionally use larger functions, duplicated loops, and specialized code where benchmarks showed measurable wins. These choices stay local to performance-sensitive encoder and decoder internals; public APIs stay small and conventional.
Acknowledgments
This library is a port of the Brotli reference implementation by the Brotli Authors, licensed under the MIT License.
Compression Speed vs Ratio
All benchmarks were taken on the following setup with turboboost, etc, being disabled via denoise-amd.sh:
goos: linux
goarch: amd64
cpu: AMD Ryzen 5 7535HS with Radeon Graphics
Compared against klauspost/compress zstd (pure Go) and stdlib gzip. Single CPU, no parallelism. These plots measure reused streaming encoders: the timed loop resets a warmed writer and discards compressed output, while ratio is measured from a warmup buffer.
| Compression | Decompression |
|---|---|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Benchmarks
Compared against other Go brotli libraries. go-brrr is the base in all comparisons. The smaller the number the better.
- andybalholm - github.com/andybalholm/brotli, pure Go encoder and decoder.
- google-brotli - github.com/google/brotli/go/brotli, Google's official pure Go decoder, transpiled from the Java reference. Decompression only, no encoder.
- cbrotli - github.com/google/brotli/go/cbrotli, Google's official cgo bindings to the C reference implementation. Including a cgo library in a pure Go comparison isn't apples-to-apples, but it is a useful comparison against Google's C implementation as exposed through its Go bindings.
One-shot Compression
The table below measures end-to-end throughput through each package's public Go API for many independent brotli streams, not only the inner compression loop. Each payload is written as a complete stream with a fresh public writer instance so cbrotli, which has no resettable writer API, can be included.
go-brrr still benefits from internal reuse in that shape: encoder arenas, hashers, hash tables, and scratch buffers are kept reusable through reset paths and internal sync.Pools. That avoids repeated large allocations and zeroing, which matters for small and mid-size payloads. cbrotli uses the C reference encoder underneath, but each payload creates a new BrotliEncoderState through cbrotli.NewWriter and destroys it on Close, paying setup, teardown, cgo, and allocation costs for every stream.
Read these rows as repeated complete-stream compression through the Go APIs. They are not a claim that every pure-Go compression hot path is faster than the C implementation; the same table shows quality levels where cbrotli is faster.
| go-brrr (sec/op) | andybalholm (sec/op) | cbrotli (sec/op) | |
|---|---|---|---|
| CompressOneshot/q=0/payload=VariedPayloads | 6.515m ± 1% | 12.420m ± 0% +90.64% (p=0.000 n=8) | 6.847m ± 0% +5.10% (p=0.000 n=8) |
| CompressOneshot/q=1/payload=VariedPayloads | 9.797m ± 0% | 20.169m ± 0% +105.88% (p=0.000 n=8) | 10.808m ± 0% +10.32% (p=0.000 n=8) |
| CompressOneshot/q=2/payload=VariedPayloads | 14.68m ± 0% | 38.86m ± 2% +164.76% (p=0.000 n=8) | 17.93m ± 0% +22.14% (p=0.000 n=8) |
| CompressOneshot/q=3/payload=VariedPayloads | 16.06m ± 0% | 44.34m ± 1% +176.12% (p=0.000 n=8) | 20.85m ± 0% +29.87% (p=0.000 n=8) |
| CompressOneshot/q=4/payload=VariedPayloads | 25.68m ± 0% | 61.37m ± 1% +138.97% (p=0.000 n=8) | 29.96m ± 0% +16.67% (p=0.000 n=8) |
| CompressOneshot/q=5/payload=VariedPayloads | 36.41m ± 0% | 80.19m ± 1% +120.22% (p=0.000 n=8) | 47.13m ± 0% +29.43% (p=0.000 n=8) |
| CompressOneshot/q=6/payload=VariedPayloads | 43.87m ± 0% | 90.57m ± 1% +106.47% (p=0.000 n=8) | 54.78m ± 0% +24.88% (p=0.000 n=8) |
| CompressOneshot/q=7/payload=VariedPayloads | 50.34m ± 0% | 127.34m ± 1% +152.96% (p=0.000 n=8) | 110.24m ± 0% +119.00% (p=0.000 n=8) |
| CompressOneshot/q=8/payload=VariedPayloads | 58.95m ± 0% | 146.62m ± 1% +148.72% (p=0.000 n=8) | 81.03m ± 1% +37.46% (p=0.000 n=8) |
| CompressOneshot/q=9/payload=VariedPayloads | 74.02m ± 0% | 209.77m ± 2% +183.41% (p=0.000 n=8) | 237.70m ± 0% +221.15% (p=0.000 n=8) |
| CompressOneshot/q=10/payload=VariedPayloads | 1221.7m ± 2% | 1368.5m ± 1% +12.01% (p=0.000 n=8) | 864.3m ± 0% -29.26% (p=0.000 n=8) |
| CompressOneshot/q=11/payload=VariedPayloads | 2.939 ± 1% | 3.424 ± 1% +16.51% (p=0.000 n=8) | 2.266 ± 0% -22.89% (p=0.000 n=8) |
| geomean | 52.96m | 111.1m +109.76% | 67.47m +27.40% |
Streaming uses brrr.NewReader + io.ReadAll; one-shot uses brrr.Decompress on a complete in-memory blob.
Streaming Decompression
As cbrotli doesn't have the "resettable" API it's not included here.
| go-brrr (sec/op) | andybalholm (sec/op) | |
|---|---|---|
| Decompress/q=4/payload=VariedPayloads | 5.378m ± 0% | 9.539m ± 0% +77.36% |
| Decompress/q=5/payload=VariedPayloads | 5.302m ± 0% | 9.143m ± 0% +72.43% |
| Decompress/q=6/payload=VariedPayloads | 5.146m ± 0% | 8.881m ± 0% +72.56% |
| Decompress/q=11/payload=VariedPayloads | 5.621m ± 0% | 8.959m ± 0% +59.37% |
| geomean | 5.359m | 9.127m +70.30% |
One-shot Decompression
| go-brrr (sec/op) | andybalholm (sec/op) | cbrotli (sec/op) | google-brotli (sec/op) | |
|---|---|---|---|---|
| DecompressOneshot/q=4/payload=VariedPayloads | 5.458m ± 0% | 10.042m ± 0% +84.01% | 5.191m ± 2% -4.89% | 10.595m ± 0% +94.13% |
| DecompressOneshot/q=5/payload=VariedPayloads | 5.458m ± 1% | 9.609m ± 0% +76.07% | 5.022m ± 11% ~ | 10.541m ± 0% +93.14% |
| DecompressOneshot/q=6/payload=VariedPayloads | 5.329m ± 1% | 9.384m ± 0% +76.11% | 4.916m ± 4% -7.74% | 10.240m ± 1% +92.17% |
| DecompressOneshot/q=11/payload=VariedPayloads | 5.816m ± 1% | 9.540m ± 0% +64.03% | 6.981m ± 1% +20.03% | crashed |
| geomean | 5.512m | 9.641m +74.91% | 5.469m -0.78% | 10.46m +93.14% |
The VariedPayloads benchmark rotates through a heterogeneous mix of files, guarding against benchmark-shaped optimizations - wins that only show up when the same input is fed back-to-back should not move these rows. Payloads span small JSON API responses, mid-size HTML and JS bundles, and larger English prose, drawn from the Brotli reference test corpus and the local testdata/ directory.
| File | Size | Source |
|---|---|---|
| github_events_2k.json | 2.2 KB | testdata |
| github_events_5k.json | 5.2 KB | testdata |
| github_events_8k.json | 8.3 KB | testdata |
| asyoulik.txt | 122 KB | brotli-ref |
| alice29.txt | 149 KB | brotli-ref |
| gh_172KB.html | 167 KB | testdata |
| reactcore_187KB.js | 182 KB | testdata |
| lcet10.txt | 417 KB | brotli-ref |
| plrabn12.txt | 471 KB | brotli-ref |





