Golang Maps: how Swiss Tables replaced the old bucket design

Terretta1 pts0 comments

Golang Maps: How Swiss Tables Replaced the Old Bucket Design

Golang Maps: How Swiss Tables Replaced the Old Bucket Design

Fri Jul 24 2026

tutorials

golang

runtime internals

data structures

performance

Introduction

Maps sit on the hot path of almost every non-trivial Go program: they power request routing, cache lookups, deduplication sets, aggregation pipelines, and a lot of the glue code we barely notice until it gets slow. Because they are so common, even small runtime-level improvements in map behavior can produce measurable gains across real systems.

Go 1.24 shipped one of the biggest map-internals changes in years: the classic bucket-plus-overflow implementation has been replaced by a Swiss Table-inspired design. The external API did not change, your code still writes map[K]V and calls make, indexing, delete, and range the same way as before. Under the hood, however, lookup and insert paths were reshaped around tighter metadata, flatter probing patterns, and much better cache locality.

The practical effect is straightforward: less pointer chasing, fewer cache misses, higher useful load factors, and faster common operations in many workloads. In microbenchmarks this can be dramatic, while full applications usually see smaller but still meaningful aggregate wins. Memory behavior also improves in many scenarios, especially where old overflow chains used to accumulate.

This post focuses on what changed specifically in Go's runtime design, why those choices matter, and where the trade-offs still show up. If you want a conceptual refresher on hash tables first, see Hash Map Deep Dive.

Go's Old Map Implementation (Pre-1.24)

Before Go 1.24, maps used a bucketed design that had been refined for years and worked well for a wide range of workloads. Each map owned an array of buckets. Each bucket held up to 8 key/value pairs, plus metadata used to speed up matching and track slot state. When a bucket filled up, the runtime allocated an overflow bucket and chained it to the original one.

At a high level, the layout looked like this:

The key idea was simple and practical. Hash the key, use part of the hash to pick the bucket, then scan that bucket's entries. If no matching key was found and an overflow bucket existed, keep walking the chain until the key was found or the chain ended.

In simplified form, the core structures were roughly:

// Conceptual shape, not the exact runtime source.<br>type hmap struct { // map header<br>count int<br>B uint8 // number of buckets is 1<br>buckets *bmap<br>oldbuckets *bmap // previous bucket array during growth

type bmap struct { // bucket with 8 slots<br>tophash [8]uint8<br>keys [8]K<br>values [8]V<br>overflow *bmap

This design had real strengths: it was stable, battle-tested, and supported incremental growth so resize work did not arrive as one huge latency spike. During growth, the map kept both old and new bucket arrays for a while, and operations gradually evacuated old buckets into their new positions as the map was accessed.

The main cost was memory locality under pressure. Overflow chains introduced pointer chasing, and pointer chasing means cache misses. Once hot buckets started spilling into overflow, lookups and inserts could bounce across non-contiguous memory. The implementation also had practical load-factor limits around 81 percent (roughly 6.5 filled slots per 8-slot bucket) before growth pressure and collision costs became harder to ignore.

So the old map was not a broken design waiting to be replaced, but an effective implementation with trade-offs that became more visible as modern CPUs, cache behavior, and high-throughput services pushed for tighter, flatter probe paths.

Swiss Tables: Core Design

Historically, Swiss Tables came out of Google's internal performance work on hash tables and were later documented and open-sourced through Abseil — Google's open-source C++ library collection — as flat_hash_map and related containers. The design notes are still the best primary reference for the model and its trade-offs.

Swiss Tables keep the same high-level hash-table contract, but reorganize the data path around two ideas: compact per-slot metadata and probe-friendly contiguous groups . The design has since been adopted across several runtimes and databases because it moves the common-case probe work onto a much more cache-efficient path.

The first thing to understand is that Swiss Tables do not start probing by reading full keys. They start by reading metadata bytes that are cheap to scan in bulk. Only candidates survive to full key comparison. This sounds small, but it changes where CPU time goes.

Hash split: one part for placement, one part for filtering

A key is hashed once, then split into two logical pieces:

h1: used to choose the initial group index.

h2: a short fingerprint stored in per-slot metadata.

You can think of h2 as a fast pre-check. If a slot's fingerprint does not match, there is no reason to touch that slot's key bytes at all. Most probes end...

bucket design tables swiss overflow hash

Related Articles