// STUHUB · C++ DATA STRUCTURES

Hash Tables: Hashing, Collisions, Load Factor, and Open Addressing vs Chaining

How hash tables turn a key into an array index, what happens when two keys collide, why the load factor triggers a rehash, and the exam-answer comparison of chaining versus open addressing.

Introduction

A hash table stores key–value pairs and answers find, insert, and erase in O(1) average time by mapping each key to an array slot with a hash function. The whole subject is really about one problem: two different keys can map to the same slot (a collision), and every design decision — chaining, open addressing, load factor, rehashing — is a strategy for surviving that fact. This note walks the pipeline from key to slot, then the two collision strategies exams compare.

From key to slot: the pipeline

The path from a key to a bucket has three stages:

  1. Hash: the key is converted to an integer. For strings, a polynomial rolling hash (h = h·31 + c) is the classic exam example; for integers, the identity is common but poor for structured keys.
  2. Compress: the integer is squeezed into the table's index range, almost always with modulo: index = h(key) % capacity.
  3. Resolve: if that slot is already occupied by a different key, apply the collision strategy.

A good hash function has two properties: it distributes keys uniformly (no slot systematically favored), and it is deterministic (same key, same slot, every time — otherwise lookups break immediately). C++'s std::hash provides stage 1 for built-in types; the container handles 2 and 3.

cpp
// The classic exam-sized version: string key, int value, chaining.
struct Node {
    std::string key;
    int value;
    Node* next;
};

class SimpleHashMap {
    static const int CAPACITY = 101;      // prime reduces clustering
    Node* buckets[CAPACITY] = {};

    static size_t hash(const std::string& s) {
        size_t h = 0;                     // polynomial rolling hash
        for (char c : s) h = h * 31 + static_cast<unsigned char>(c);
        return h;
    }
public:
    void put(const std::string& k, int v) {
        size_t i = hash(k) % CAPACITY;
        for (Node* p = buckets[i]; p; p = p->next)
            if (p->key == k) { p->value = v; return; }   // update
        buckets[i] = new Node{k, v, buckets[i]};          // prepend
    }
    bool get(const std::string& k, int& out) const {
        for (Node* p = buckets[hash(k) % CAPACITY]; p; p = p->next)
            if (p->key == k) { out = p->value; return true; }
        return false;
    }
};

Collision strategy 1: separate chaining

Each slot holds a pointer to a linked list of all keys that hashed there. Insertion prepends to the list (O(1)); lookup walks the list comparing keys with ==.

Why it works: collisions stop being a problem and become ordinary list entries. The cost: average lookup is O(1 + α) where α is the load factor (n / capacity) — with a good hash, lists stay short and this is effectively constant. The worst case: all keys collide into one slot, degrading to O(n) — the pathological case every exam asks you to name (usually caused by a constant hash function or adversarial keys).

Collision strategy 2: open addressing

No lists: every entry lives in the table itself. On collision, probe for another slot. The three probe sequences to know:

  • Linear probing: index = (h + i) % capacity for i = 0, 1, 2, ... — cache-friendly but suffers primary clustering (long runs of occupied slots grow longer).
  • Quadratic probing: index = (h + i²) % capacity — breaks up runs, but suffers secondary clustering among keys with the same base hash.
  • Double hashing: index = (h1 + i·h2(k)) % capacity — the second hash gives each key its own stride; fewest clustering problems.

Deletion is the classic trap: emptying a slot breaks the probe chain of later keys, so you mark it with a tombstone instead of clearing it. That is open addressing's signature complexity in practice.

Load factor and rehashing

The load factor α = n / capacity measures how full the table is. For chaining, average cost degrades gracefully with α; for open addressing, performance falls off a cliff as α → 1 because probe sequences lengthen fast. Both strategies therefore pick a threshold (commonly 0.75) and rehash when it is exceeded: allocate a bigger array (typically double), then re-insert every element — its index changes because h(key) % capacity changed.

Rehashing is O(n), but amortized over the n insertions that caused it, insertion remains O(1) average — the same accounting trick as a dynamic array's growth.

Chaining vs open addressing: the exam table

CriterionSeparate chainingOpen addressing
Extra memoryPointer per entry + list nodesNone beyond the table
Worst-case lookupO(n) (all collide)O(n) (table nearly full)
Performance at α = 0.75Fine (short lists)Degrading (long probes)
DeletionTrivial (unlink)Needs tombstones
Cache behaviorPoor (pointer chasing)Good (contiguous)

std::unordered_map is specified as a hash table with chaining and O(1) average operations — which is why the standard answer to "which container for O(1) lookup by key" is unordered_map, while "sorted order iteration" points back to std::map (a balanced BST, O(log n)).

Exercises with solutions

Work through each question before opening the solution below it.

Exercise 1

Insert the keys 4, 17, 30 into a hash table of capacity 7 using h(k) = k % 7 with separate chaining. Show the final table. Then insert 24 — what happens?

Solution

4 % 7 = 4 → bucket 4. 17 % 7 = 3 → bucket 3. 30 % 7 = 2 → bucket 2.

Buckets: 0:— 1:— 2:[30] 3:[17] 4:[4] 5:— 6:—

24 % 7 = 3 → collision with 17; chaining appends to bucket 3, which becomes [17 → 24] (or [24 → 17] if prepending). Nothing is overwritten — the two keys coexist in the list, which is exactly what chaining guarantees.

Exercise 2

Same keys (4, 17, 30, then 24) with linear probing, capacity 7. Show the probe sequences.

Solution

4 → slot 4 free → placed at 4. 17 → slot 3 free → placed at 3. 30 → slot 2 free → placed at 2. 24 → slot 3 occupied (17) → probe (3+1)%7 = 4 occupied (4) → probe 5 free → placed at 5.

Note how 24 probed past two different keys — under linear probing a collision cluster absorbs unrelated keys, which is primary clustering in miniature.

Exercise 3

A table using open addressing currently holds a key at slot 9 that came from a probe starting at 7. You erase slot 8 (a different key) by setting it to empty. Why might a later lookup for the slot-9 key now fail, and what is the standard fix?

Solution

The slot-9 key is only reachable by the probe 7 → 8 → 9. Making slot 8 truly empty cuts the chain: a lookup probes 7 (wrong key), then 8 (now empty) and concludes absent — a false negative. The standard fix is a tombstone: mark slot 8 as deleted rather than empty, so probes continue past it while insertions may reuse it. Periodically, a rehash clears accumulated tombstones.

Exercise 4

Your hash table with chaining has capacity 10 and currently holds 90 keys. What is the load factor, what does it imply for performance, and what action does the design call for?

Solution

α = 90/10 = 9. Average successful lookup costs O(1 + α) ≈ 10 key comparisons — no longer constant in practice. The design calls for a rehash: allocate a larger table (commonly 2× or the next prime near that), recompute each key's index with the new capacity, and re-insert; α drops to ≈4.5 immediately and to ≈2.25 after doubling twice. The O(n) rehash is amortized against the insertions that filled the table.

Which C++ standard does this page assume?

Standard C++17. C++ began as C with Classes in 1979 and first shipped commercially in 1985; what a compiler flag actually selects today is one of the ISO revisions below, and the committee has published a new one every 3 years since 2011. Build these listings with -std=c++17 and they compile as written.

C++ ISO revisions, their publication identifiers, and what each one changes for the code on this page
RevisionPublished asWhat it changes for the code on this page
C++98ISO/IEC 14882:1998The first ISO C++, and the dialect most data-structures courses still teach from: templates, the STL containers, and raw pointers doing the work.
C++03ISO/IEC 14882:2003A defect-fix revision. Nothing on these pages depends on it, and nothing on these pages is broken by it.
C++11ISO/IEC 14882:2011Where nullptr, auto, range-based for, move semantics and the unordered containers arrive. Every listing here writes nullptr rather than NULL because of it.
C++14ISO/IEC 14882:2014A small revision: generic lambdas and std::make_unique. Used only where it makes ownership clearer.
C++17ISO/IEC 14882:2017What every listing on StuHub targets and was compiled against. If you build these files, build them with -std=c++17.
C++20ISO/IEC 14882:2020Concepts, ranges and std::midpoint. Flagged in the prose where it offers a shorter correct form, never assumed by the code.
C++23ISO/IEC 14882:2024Not used here. Named so you can tell whether a snippet you found elsewhere will compile on a lab machine that predates it.

Common questions

What does this page cover?

How hash tables turn a key into an array index, what happens when two keys collide, why the load factor triggers a rehash, and the exam-answer comparison of chaining versus open addressing.

How long does this page take to work through?

About 5 minutes of reading at 200 words per minute, plus 4 questions with worked solutions at the foot of the page. Reading it end to end is the slow way; the intended use is to find the section you are stuck on, then do the questions for that section with the solutions covered.

Which C++ standard do these examples target?

Standard C++17 — ISO/IEC 14882:2017. Every listing was compiled with -std=c++17 and -Wall -Wextra before publication, and the linked-structure examples were also run under AddressSanitizer. Where C++20 offers a shorter correct form, such as std::midpoint, the prose says so instead of quietly using it.

Is StuHub free, and do I need an account?

It is free and there is nothing to sign in to. No login, account or payment is required to read any of the 20 topics — StuHub is published by DUOCODE TECHNOLOGY alongside APRide, and the ride board's accounts have nothing to do with it.

Can I paste this code into my assignment?

Treat it as a reference, not as an answer key. StuHub is educational material only, it is not coursework and it is not affiliated with or endorsed by any institution, so your own submission rules decide what you may reuse. Every listing was compiled and run before publication, and you should still compile and test anything you take.

Why is everything written in C++ rather than pseudocode?

Because most of the mistakes worth catching are C++ mistakes, not algorithm mistakes: a lost pointer, a destructor that never runs, an index that underflows because it was unsigned. Pseudocode hides exactly the layer where a data-structures assignment is actually failed.

Where should I check what the standard library really guarantees?

cppreference for the day-to-day answer, and the WG21 working drafts when the exact wording matters — both are linked below. Compiler documentation settles the rest: a warning you cannot explain is usually the compiler being right.

Does StuHub replace my lecture notes?

No. It is written to sit beside them: your course decides what is examinable, in what notation, and with which library restrictions. Where this page and your module handbook disagree about scope, the handbook wins.

Is StuHub connected to Asia Pacific University?

No. It is an independent reference published by DUOCODE TECHNOLOGY, not affiliated with or endorsed by Asia Pacific University or any other institution. It was written for APU students because that is who asked for it, and it is open to anyone.

Where can I check this against the language itself?

Nothing on this page outranks the standard or the library reference. When this page and one of these disagree, they are right and we want to know.

Published 2026-08-09 · updated 2026-08-27 · DUOCODE TECHNOLOGY