Zig HashMaps
A HashMap stores key-value pairs and lets you look up a value by its key in near-constant time. Instead of searching through a list one by one, the hash map computes a position from the key and jumps directly to it. Zig's standard library provides several hash map types that suit different use cases.
The Hash Map Concept
Array lookup by index: HashMap lookup by key:
data[3] → instant map.get("Alice") → instant
(index must be a number) (key can be any type)
Internal mechanism:
key "Alice"
│
hash("Alice") = 83741...
│
83741 % bucket_count = 7
│
bucket[7] → value 95
StringHashMap — String Keys
The most common case: keys are strings, values are any type.
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var scores = std.StringHashMap(u32).init(alloc);
defer scores.deinit();
// Insert entries
try scores.put("Alice", 95);
try scores.put("Bob", 82);
try scores.put("Carol", 91);
try scores.put("Deepak", 78);
// Lookup
if (scores.get("Alice")) |score| {
std.debug.print("Alice: {d}\n", .{score}); // 95
}
// Check existence
std.debug.print("Has Eve: {}\n", .{scores.contains("Eve")}); // false
// Update an existing key
try scores.put("Bob", 88); // overwrites 82 with 88
std.debug.print("Bob updated: {d}\n", .{scores.get("Bob").?}); // 88
// Count
std.debug.print("Entries: {d}\n", .{scores.count()}); // 4
}
Iterating a HashMap
var it = scores.iterator();
while (it.next()) |entry| {
std.debug.print("{s} → {d}\n",
.{entry.key_ptr.*, entry.value_ptr.*});
}
Output (order is not guaranteed): Carol → 91 Alice → 95 Bob → 88 Deepak → 78
Hash map iteration order is not guaranteed to match insertion order. The hash function distributes keys across buckets, and the iteration visits buckets in bucket order, not insertion order. If insertion order matters, use std.ArrayList of pairs or std.ArrayHashMap.
getOrPut — Insert If Missing
getOrPut either retrieves an existing entry or creates a new one. This avoids two separate lookups when you want to insert-if-absent:
const result = try scores.getOrPut("Frank");
if (result.found_existing) {
std.debug.print("Frank already exists: {d}\n", .{result.value_ptr.*});
} else {
result.value_ptr.* = 70; // set default value
std.debug.print("Frank added with score 70\n", .{});
}
"Frank" not in map: getOrPut → found_existing=false → set value_ptr.* → inserted "Frank" already in map: getOrPut → found_existing=true → read value_ptr.* → no insert
Removing Entries
const removed = scores.remove("Bob");
std.debug.print("Bob removed: {}\n", .{removed}); // true
const missing = scores.remove("Eve");
std.debug.print("Eve removed: {}\n", .{missing}); // false
AutoHashMap — Non-String Keys
When your keys are integers, enums, or other non-string types, use AutoHashMap. It generates a hash function automatically based on the key type:
var word_count = std.AutoHashMap(u8, u32).init(alloc);
defer word_count.deinit();
const text = "hello world zig is great";
for (text) |ch| {
if (ch == ' ') continue;
const entry = try word_count.getOrPut(ch);
if (!entry.found_existing) entry.value_ptr.* = 0;
entry.value_ptr.* += 1;
}
var it = word_count.iterator();
while (it.next()) |e| {
std.debug.print("'{c}' appears {d} time(s)\n",
.{e.key_ptr.*, e.value_ptr.*});
}
Counting letter 'l':
"hello world zig is great"
↑ ↑ ↑
'l' appears 3 times
ArrayHashMap — Ordered by Insertion
std.ArrayHashMap preserves insertion order, making iteration predictable:
var ordered = std.StringArrayHashMap(u32).init(alloc);
defer ordered.deinit();
try ordered.put("first", 1);
try ordered.put("second", 2);
try ordered.put("third", 3);
// Iterates in insertion order: first, second, third
for (ordered.keys(), ordered.values()) |k, v| {
std.debug.print("{s}: {d}\n", .{k, v});
}
Choosing the Right HashMap
Type │ Key type │ Order │ Use case ────────────────────────┼──────────────┼────────────┼───────────────── StringHashMap(V) │ []const u8 │ None │ String keys AutoHashMap(K, V) │ Any │ None │ Int/enum keys HashMap(K, V, Ctx, ...) │ Any+context │ None │ Custom hash fn StringArrayHashMap(V) │ []const u8 │ Insertion │ Ordered string AutoArrayHashMap(K, V) │ Any │ Insertion │ Ordered keys
Practical Example: Word Frequency Counter
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var freq = std.StringHashMap(u32).init(alloc);
defer freq.deinit();
const words = [_][]const u8{
"zig", "is", "fast", "zig", "is", "safe", "zig",
};
for (words) |word| {
const entry = try freq.getOrPut(word);
if (!entry.found_existing) entry.value_ptr.* = 0;
entry.value_ptr.* += 1;
}
var it = freq.iterator();
while (it.next()) |e| {
std.debug.print("{s}: {d}\n", .{e.key_ptr.*, e.value_ptr.*});
}
}
Output (order may vary):
zig: 3 is: 2 fast: 1 safe: 1
