#️⃣

Hash Table: Computer Science Study Notes

October 10, 2026

📚 Comprehensive Guide to Hash Tables

  • Core Concepts & Overview: Associative arrays, hash functions, and load factor mechanics
  • Hash Function Design: Integer universe assumptions, specific algorithms, and uniformity requirements
  • Collision Resolution Strategies: Separate chaining vs. open addressing and advanced variants
  • Dynamic Resizing: Full rehashing, incremental approaches, and linear hashing
  • Performance Analysis: Time complexities, distribution impacts, and statistical expectations
  • Practical Applications: Database indexing, caching, sets, and transposition tables
  • Language Implementations: Built-in hash table support across popular programming languages

💡 Overview of Hash Tables

In computer science, a hash table is a fundamental data structure that implements an associative array (also called a dictionary or map). An associative array is an abstract data type that maps keys to values.

  • Key-Value Mapping: An array AA of length mm stores nn elements, where m≥nm \geq n.
  • Hash Indices: A hash table uses a hash function hh to compute an index (or hash code) A[h(x)]A[h(x)], where h(x)<mh(x) < m.
  • Storage Strategy: Both the key and its associated value are stored at the computed index. Storing the key alongside the value ensures that lookups can verify the key to retrieve the correct value, even when collisions occur.
  • Hash Maps: A map implemented via a hash table is commonly referred to as a hash map.
[Key] ---> [ Hash Function ] ---> [ Hash Code / Index ] ---> [ Bucket Array A[h(x)] ]

The Space-Time Tradeoff

Hashing is a classic example of a space–time tradeoff:

  • Infinite Memory: The entire key can be used directly as an index to locate its value with a single memory access.
  • Infinite Time: Values can be stored without regard for their keys, relying on a binary search or linear search to retrieve elements.

⚖️ Load Factor and Performance

The efficiency of a hash table heavily depends on its load factor (α\alpha), defined as the ratio of stored elements to available slots:

load factor (α)=nm\text{load factor}\ (\alpha) = \frac{n}{m}

Where:

  • nn = number of key-value pairs in the hash table
  • mm = number of buckets

Statistical Distribution

In the limit of large mm and nn, each bucket statistically follows a Poisson distribution with expectation λ=α\lambda = \alpha for an ideally random hash function.

Maximum Load Factors by Resolution Strategy

StrategyMaximum Load Factor (αmax⁡\alpha_{\max}) BehaviorRecommended αmax⁡\alpha_{\max} Range
Open AddressingCannot exceed 11 because each slot holds exactly one item. Performance degrades severely as α→1\alpha \to 1.0.60.6 to 0.750.75
Separate ChainingPerformance declines gradually rather than sharply. No fixed threshold requires absolute resizing at α=1\alpha = 1.11 to 33

🧮 Hash Functions

A hash function h:U→{0,...,m−1}h: U \rightarrow \{0, ..., m-1\} maps the universe of keys UU to indices or slots within the table.

  • Integer Universe Assumption: Conventional implementations assume all elements stem from a universe U={0,...,u−1}U = \{0, ..., u-1\}, where the bit length of uu fits within the architecture's word size.
  • Perfect Hash Function: A hash function is perfect for a given set SS if it is injective on SS (each element maps to a distinct value). This is achievable when all keys are known in advance.

Common Hashing Schemes

1. Hashing by Division

The most commonly used scheme: h(x) = x  mod  mh(x)\ =\ x\,{\bmod {\,}}m where h(x)h(x) is the hash value and mm is the table size.

2. Hashing by Multiplication

h(x)=⌊m((xA) mod 1)⌋h(x)=\lfloor m{\bigl (}(xA){\bmod {1}}{\bigr )}\rfloor where AA is a non-integer real-valued constant.

  • Advantage: The choice of table size mm is not critical.
  • Recommendation: Donald Knuth suggests using the golden ratio for constant AA.

3. String Hashing

  • C++ Style: An unsigned integer (initially zero) is repeatedly left-shifted by one bit and XORed with the integer value of the next character, then modulated by table size.
  • Polynomial Rolling Hash: Another widespread method for hashing strings into integers.

Choosing a Hash Function

  • Uniform Distribution: Fundamental requirement. Non-uniform distributions increase collisions and resolution costs. Evaluate using statistical tests like Pearson's chi-squared test.
  • Run Avoidance: For open addressing, functions must avoid runs (mapping multiple keys to consecutive slots), which destroys lookup performance. Multiplicative hashes can exhibit particularly poor run behavior.
  • KK-Independent Hashing: Proves that a hash function does not feature bad keysets for specific table types, helping isolate the fastest possible function.

⚔️ Collision Resolution

Because most hash designs employ imperfect hash functions, hash collisions (where multiple keys generate the same index) must be accommodated.


1. Separate Chaining

In separate chaining, collided items are stored together using a linked list or alternative data structure at each array index.

Core Operations (Pseudocode)

Chained-Hash-Insert(T, x)
  insert x at the head of linked list T[h(k)]

Chained-Hash-Search(T, k)
  search for an element with key k in linked list T[h(k)]

Chained-Hash-Delete(T, x)
  delete x from the linked list T[h(k)]

Advanced Chaining Data Structures

  • Self-Organizing Trees: Using self-balancing binary search trees for ordered keys brings the theoretical worst-case down to O(log⁡n)O(\log n).
  • Dynamic Perfect Hashing: Employs two-level hash tables to guarantee O(1)O(1) worst-case lookup time. Buckets of kk entries are organized as perfect hash tables with k2k^2 slots. Array-based separate chaining can be up to 97% more performant than standard linked lists under heavy loads.
  • Cache-Conscious Variants: Replacing linked lists with contiguous dynamic arrays exploits hardware-cache prefetchers (like TLBs), drastically reducing access time and memory consumption.

2. Open Addressing

In open addressing, every record is stored directly within the bucket array. Probing sequences determine alternative slots when a collision occurs.

Well-Known Probing Sequences

  • Linear Probing: Fixed interval between probes (usually 11). Offers high CPU cache utilization due to spatial locality.
  • Quadratic Probing: Probing intervals are increased by adding successive outputs of a quadratic polynomial.
  • Double Probing / Double Hashing: The probe interval is computed using a secondary hash function.

Open Addressing Variants

Coalesced Hashing
  • A hybrid of separate chaining and open addressing. Colliding elements are placed in the largest-indexed available slot and linked to the original bucket via a next pointer. Ideal for fixed memory allocation.
Cuckoo Hashing
  • Maintains two hash tables, each with its own hash function.
  • If a slot is occupied, the resident item is displaced into the alternate table.
  • Guarantees O(1)O(1) worst-case lookup complexity. If an infinite loop is detected via a threshold counter, both tables are rehashed with new functions.
Hopscotch Hashing
  • Combines cuckoo hashing, linear probing, and separate chaining using the concept of a neighborhood of buckets (virtual buckets).
  • Optimized for load factors exceeding 90%90\% and concurrent settings.
  • Utilizes an H-bit bit array ("hop-information") in each bucket to indicate the relative distance of items originally hashed to that virtual bucket.
Robin Hood Hashing
  • Favors displacing the element with the longest probe sequence length (PSL) from its home location.
  • Dramatically reduces the variance of item distribution and long run formations.
  • Nodes are augmented with an extra psl value. During insertion, if the incoming item has a strictly greater PSL than the current table item, they are swapped and probing continues.

🔄 Dynamic Resizing

To maintain amortized O(1)O(1) performance as the load factor grows, hash tables undergo dynamic resizing and rehashing. Conversely, tables may shrink if they become too empty.

Resizing Strategies

StrategyMechanismTradeoffs
All-at-Once RehashingAllocates a new table twice the size of the original and moves every item by recomputing hash values.Simple to implement, but computationally expensive and unsuitable for real-time systems.
Incremental RehashingGradually cleans buckets using wrapper commands (Add, Get, Delete) alongside two hash functions (holdh_{\text{old}} and hnewh_{\text{new}}).Avoids storage spikes, latency interruptions, and memory fragmentation.
Linear HashingGrows or shrinks the hash table dynamically one bucket at a time.Provides smooth, incremental capacity adjustments.

📈 Performance Summary

  • Expected Time with Chaining (assuming uniform distribution):
    • Successful search: 1+α2+Θ(1m)1 + \frac{\alpha}{2} + \Theta\left(\frac{1}{m}\right)
    • Unsuccessful search: e−α+α+Θ(1m)e^{-\alpha} + \alpha + \Theta\left(\frac{1}{m}\right)
  • Core Dependency: Hash table performance relies entirely on the hash function's ability to disperse indices uniformly and prevent clustering.

🌐 Practical Applications

  • Associative Arrays: Implementing in-memory dictionaries and maps.
  • Database Indexing: Disk-based data structures (e.g., dbm), though B-trees remain more common.
  • Caches: Speeding up access to slower storage media. Collisions are typically handled by discarding/overwriting the older item.
  • Sets: Storing unique values without order to test membership by omitting stored values and tracking key presence only.
  • Transposition Tables: Storing previously evaluated positions in game search trees.

💻 Language Implementations

Many modern programming languages provide built-in hash table abstractions:

  • JavaScript: Object collections map string/symbol keys to values (primitive keys coerced to strings). ECMAScript 2015 introduced the Map structure for arbitrary keys.
  • C++: std::unordered_map (C++11) stores arbitrary key-value pairs in standard libraries.
  • Go: Built-in map type implemented via hash tables.
  • Java: Generic collections including HashMap, HashSet, LinkedHashMap, and LinkedHashSet.
  • Python: Built-in dict type implements high-performance hash tables.
  • Ruby: Built-in Hash utilizing open addressing models (since Ruby 2.4).
  • Rust: HashMap and HashSet included in the standard library.
  • .NET (C#, VB.NET): Standard library provides Dictionary and HashSet.