Skip to content

Expert · Data structures in C · topic 50 of 60 · 30 min

Hash tables

Explanation

A hash table maps keys to values in about O(1) time. A hash function turns a key (like a string) into a number, which picks a bucket in an array. Different keys can land in the same bucket (a collision), so each bucket holds a small linked list (chaining). Keep the load factor (items ÷ buckets) low by growing and rehashing when it gets high.

Why hash tables?

Looking up a PIN code's city or a product's stock by name in an array means scanning every entry (O(n)). A hash table jumps almost directly to the right place. Compilers use them for symbol tables, databases for indexes and joins, caches for quick lookups, and languages build their dictionaries and maps on them.

A hash function

c
/* djb2, a simple, well-known string hash by Daniel J. Bernstein */
unsigned long hash_str(const char *s) {
    unsigned long h = 5381;
    int c;
    while ((c = (unsigned char) *s++) != 0) {
        h = h * 33 + (unsigned long) c;      /* often written ((h << 5) + h) + c */
    }
    return h;
}
size_t bucket = hash_str("Pune") % NBUCKETS;
A good hash spreads keys evenly across buckets. Unsigned arithmetic wraps on overflow, which is well defined.

Chaining

c
#define NBUCKETS 64

typedef struct Entry {
    char key[32];
    int value;
    struct Entry *next;          /* other entries in the same bucket */
} Entry;

Entry *table[NBUCKETS];          /* all NULL at start (static/global storage) */

buckets:
[0]  NULL
[1]  ──► ["Pune" | 411] ──► ["Goa" | 403] ──► NULL     (a collision: both hashed to 1)
[2]  NULL
[3]  ──► ["Delhi" | 110] ──► NULL
…
c
int ht_put(const char *key, int value) {
    size_t b = hash_str(key) % NBUCKETS;
    for (Entry *e = table[b]; e; e = e->next) {
        if (strcmp(e->key, key) == 0) { e->value = value; return 0; }   /* update */
    }
    Entry *e = malloc(sizeof *e);
    if (!e) return -1;
    snprintf(e->key, sizeof e->key, "%s", key);
    e->value = value;
    e->next = table[b];          /* insert at the head of the bucket's list */
    table[b] = e;
    return 0;
}

int ht_get(const char *key, int *out) {
    size_t b = hash_str(key) % NBUCKETS;
    for (Entry *e = table[b]; e; e = e->next) {
        if (strcmp(e->key, key) == 0) { *out = e->value; return 1; }
    }
    return 0;                    /* not found */
}

Load factor and resizing

The load factor is the number of entries divided by the number of buckets. As it rises, chains get longer and lookups slow down. Real tables grow (often doubling the bucket count) when the load factor passes a threshold such as 0.75, and rehash every entry into the new array, because the bucket index depends on the array size.

Careful: A poor hash function (for example "sum of the characters", which makes "abc" and "cba" collide) or an attacker who chooses keys that all collide can turn O(1) lookups into O(n). Use a well-tested hash, and for tables exposed to untrusted input, use a keyed hash (such as SipHash, used by several languages' standard libraries for this reason).
Think of it like this: A hash table is like a post office with numbered pigeonholes: the PIN code (hash) tells the clerk which pigeonhole to check, and inside there are only a few letters to look through. If a careless sorting rule put everyone's letters in pigeonhole 1, finding a letter would mean searching the whole pile again.
Tip: In interviews, explain three things: how the hash picks a bucket, how collisions are handled (chaining or open addressing), and why the table resizes. That covers most hash-table questions.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Hash tables · 1/6

Hash tables

In this lesson you will build a hash table, the structure behind fast lookups.

Prefer a video by a teacher? Find free videos on “hash table in c chaining djb2 load factor rehashing” on YouTube ↗

In real life: Counting product scans at a supermarket

A supermarket's end-of-day report counts how many times each barcode was scanned. Loading 200,000 scan events into a hash table keyed by barcode gives each product's count in a single pass, instead of searching a list for every scan.

c
int count;
for (size_t i = 0; i < n_scans; i++) {
    if (!ht_get(scans[i], &count)) count = 0;
    ht_put(scans[i], count + 1);
}

Practice (0/3 done)

Task 1 · Question

A table has 100 buckets and 300 entries. What is the load factor?

Task 2 · Code · Javascript

The same algorithms in JavaScript, which runs here: write djb2(str) returning the 32-bit unsigned djb2 hash (start at 5381; for each character code c: h = (h * 33 + c) >>> 0), and a tiny chained table: createTable(nBuckets) with put(key, value), get(key) (undefined if missing) and bucketSizes() returning an array of chain lengths. Use arrays of [key, value] pairs as the chains, NOT a Map.

Task 3 · Practice

Implement the chained hash table in C with ht_put, ht_get, ht_remove and ht_free, plus a resize function that doubles the bucket array and rehashes all entries when the load factor exceeds 0.75. Load 1,000 words from a file (or generated strings), print the longest chain before and after resizing, and check for leaks.

Quick quiz

Question 1

1.What is a collision?

Question 2

2.Why must entries be rehashed when the table grows?

Question 3

3.What is the average lookup time of a well-sized hash table?

Question 4

4.Why is "sum of characters" a poor string hash?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is a collision?

Answer: Two different keys mapping to the same bucket

Chaining or open addressing handles it.

Why must entries be rehashed when the table grows?

Answer: The bucket index depends on the number of buckets

hash % new_size differs from hash % old_size.

What is the average lookup time of a well-sized hash table?

Answer: O(1)

With a good hash and low load factor.

Why is "sum of characters" a poor string hash?

Answer: Anagrams like "abc" and "cba" always collide, and values cluster

Order is ignored.

In a hash table, what is a collision?Related

Answer: Two different keys that land in the same bucket

More possible keys than buckets means some keys must share a bucket; chaining or open addressing handles it.

From Hash maps and hash sets

What is the time to find a key in std::map with n keys?Related

Answer: O(log n)

std::map is a balanced tree, so its height is about log n.

From std::map and std::unordered_map

How long is a SHA-256 hash written in hex?Related

Answer: 64 characters

256 bits = 32 bytes = 64 hex characters, whatever the input size.

From Hashing: fingerprints for data

Top-k words with the rule "higher count first, then alphabetical". Which sort key?Related

Answer: key=lambda w: (-counts[w], w)

The minus sign sorts counts from high to low; the word breaks ties alphabetically.

From Counting and grouping with hashing

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Data structures in C”. The module exam comes next.
  2. Module 10 exam: Data structures in C · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Debugging and tools. 60 topics left in the course before the final exam.

← Previous: Binary search trees