VictoriaMetrics breaks down how Go's map uses Swiss Tables

VictoriaMetrics published a blog post explaining how Go's built-in map has worked internally since Go 1.24 swapped the old hash-map runtime for an implementation based on Swiss Tables. The post is a follow-up to the company's earlier article on the old map design and to Go's own team post, 'Faster Go maps with Swiss Tables'; it says it covers the same material more gradually and visually, and that readers do not need the old article first.
The walkthrough starts from the runtime representation: a map variable is a pointer to an internal Map struct holding a used counter, a random per-map seed, and a pointer into the map's storage. Because used is a direct field, len(m) is O(1) rather than a scan. The seed means the same key can hash to different locations in two different maps.
A small map keeps its entries in a single group of up to 8 key-value slots. Each group has 8 control bytes on top of its 8 slots, one control byte per slot. Go hashes a key with the map's seed and splits the hash: on most 64-bit targets the upper 57 bits are H1 and the lower 7 bits are H2 (32-bit targets and Wasm use a 32-bit layout instead). H2 is stored in the slot's control byte together with one extra bit marking whether the slot is live, empty (10000000) or a deleted tombstone (11111110). A lookup can stop as soon as it hits an empty slot but must keep scanning past a tombstone. To find or update a key, Go compares the target's H2 against all 8 control bytes in the group at once, using SIMD on AMD64 to produce a bitmap of candidate slots in one step, then confirms the match with a full key comparison on just those candidates.
Once a group's 8 slots are exhausted, Go stops using a bare group and introduces a table, a structure that owns one or more groups and tracks used, capacity and a growthLeft counter. In the post's running example, the table that results from growing past one group starts out with capacity 16, used 9, and 2 groups. H1 now also does work: a key's starting group is H1 modulo the number of groups. Because entries can land unevenly, a full starting group forces Go to probe further groups in what it calls the triangular probe sequence, stepping by +1, then +2, then +3 groups and wrapping around; since the number of groups is always a power of two, this sequence visits every group exactly once before repeating. Once a map is table-backed, the Map struct's storage pointer no longer points at a group directly but at a one-entry 'directory' array whose entry points at the table.
Table growth is governed by a load factor that counts live entries plus tombstones over total slots, and Go caps a regular table at 7/8 of its slots, or 87.5%, never letting it fill completely. In the worked example, a 16-slot table holding 10 live entries and no tombstones has a load factor of 10/16, or 62.5%; its insertion limit, from 16 times 7/8, is 14 entries, so after those 10 entries its growthLeft counter reads 4. A table doubles its group count as it needs more room, and a single table can grow up to 128 groups, a maximum of 1024 slots (128 times 8). Past that ceiling, an insertion that needs more room causes Go to split the table into two tables rather than growing it further; the retrieved text breaks off before describing that split or later deletion and cleanup mechanics in detail. The post also notes that Go is separately testing, but has not shipped, an alternative group layout that stores keys and values in separate arrays to improve lookup locality and remove alignment padding.
Key facts
- Go 1.24 replaced the old built-in map runtime with an implementation based on Swiss Tables.
- A group holds up to 8 key-value slots; each slot's control byte stores a 7-bit hash fragment (H2) plus a live/empty/tombstone bit, and AMD64 uses SIMD to compare a key's H2 against all 8 control bytes in one step.
- On 64-bit targets, Go splits a key's hash into an upper 57-bit H1 (picks the starting group via H1 modulo group count) and a lower 7-bit H2 (stored in the control byte).
- A regular table caps its load factor at 7/8 (87.5%) of live entries plus tombstones; a 16-slot, 10-entry example table has a 62.5% load factor and a growthLeft of 4 out of a 14-entry limit.
- A single table can grow to at most 128 groups (1024 slots); beyond that, Go splits it into two tables instead of growing it further, using a triangular probe sequence to search groups.
Why it matters
Go 1.24 quietly changed how one of the language's most heavily used built-in types stores its data, and the change is invisible from ordinary code: make(map[K]V), indexing and range all look the same as before. This post exists because the internals are not invisible to anyone reasoning about performance, memory layout or why maps behave the way they do (unordered iteration, per-map randomization), and the old mental model of buckets and overflow chains no longer matches what the runtime actually does. VictoriaMetrics frames the piece as a gentler, more visual companion to Go's own team post on the same redesign.
Who it affects
Go engineers who read runtime source, debug map-heavy code, or simply want an accurate mental model of a type they use constantly. It also affects anyone who previously learned the pre-1.24 bucket-based map implementation, since that model is now out of date for current Go versions and the post explicitly does not assume that background.
How to use it
There is nothing to install or license here; the payoff is intuition. Knowing that a group holds 8 slots, that growth doubles group counts up to 128 groups before a table splits, and that load factor (not raw slot count) drives when growth happens gives a concrete basis for reasoning about a map's memory footprint and resizing behavior as it fills up. The mention that Go is trialing a split key/value group layout is a heads-up that this internal representation may keep changing in future releases.
How solid is it
The account comes from VictoriaMetrics's own engineering blog, cross-referencing Go's official 'Faster Go maps with Swiss Tables' post, and works through concrete structures (Map, group, table) with worked numeric examples such as the 16-slot table's 62.5% load factor and its growthLeft of 4. The retrieved text is truncated mid-sentence after the table-splitting mechanics, before it reaches deletion and tombstone cleanup, so this retelling covers only the growth path the source actually presents; no author name or publication date is given in the retrieved page.
Risks and caveats
The retrieved material contains no benchmark numbers comparing the old and new map implementations, so any performance improvement from the Swiss Table switch is not something this source substantiates. The described hash split (57-bit H1, 7-bit H2) applies to most 64-bit targets only; the post says 32-bit targets and Wasm use a different 32-bit hash layout it does not detail. The alternative split key/value group layout is explicitly experimental and not part of the shipped implementation.
“One table can grow up to 128 groups, giving it a maximum capacity of 1024 slots”
— VictoriaMetrics blog