Skip to main content

Static vs. Dynamic Hashing: Extendible & Linear Hashing

πŸ“šModule 01Topic 1.1⏱️5 min read
🎯High-Yield For:Semester Exams β€’ GATE CSE β€’ Technical Interviews

πŸ’‘ Core Intuition​

🍳 The Everyday Analogy: The Mailroom Wall vs. The Accordion Organizer​

Imagine managing incoming physical mail for employees in a growing tech company:

  1. The Static Mailroom (Fixed Cubbies): You nail a wooden shelf with exactly 1010 cubby holes (buckets) numbered 0 through 9 to the wall. You file mail using the last digit of the employee ID. When the company grows from 50 to 5,000 employees, cubbies overflow. Letters spill onto the floor, so you tape cardboard boxes underneath (overflow chains). Searching for one letter requires sifting through an endless cardboard chain (O(k)O(k) time). To fix this, you must tear down the entire wall, buy 100 new cubbies, and manually re-sort every single letter in the building (O(N)O(N) full rehash).
  2. The Dynamic Mailroom (Extendible Hashing): Instead of physical wall cubbies, you keep an expandable digital index board (The Directory) and a set of portable storage bins (Buckets). Each bin handles a binary prefix (e.g. 0 and 1). When bin 0 fills up, you do not touch bin 1! You simply divide bin 0 into 00 and 01, leaving the rest of the mailroom completely undisturbed. The directory expands smoothly and incrementally as data grows.

πŸ’» Bridging to Computer Science​

In database storage, Hashing maps search keys directly to disk block addresses using a mathematical hash function h(K)h(K), achieving theoretical O(1)O(1) point lookups. However, enterprise database tables are dynamic: rows are constantly inserted and deleted.

  • Static Hashing uses a fixed number of address buckets. Over time, it suffers from performance degradation (long overflow chains) or massive storage waste.
  • Dynamic Hashing (Extendible Hashing and Linear Hashing) allows the hash address space to grow and shrink smoothly without requiring a complete database reorganization.


πŸ“š Core Deep-Dive & Architectural Concepts​

1. Static Hashing Architecture & Inherent Flaws​

Definition: In Static Hashing, a fixed collection of MM physical disk blocks (called Buckets) is allocated on disk numbered from 00 to Mβˆ’1M - 1.

A deterministic hash function maps any search key KK to a bucket address: h(K)β†’{0,1,2,…,Mβˆ’1}h(K) \rightarrow \{0, 1, 2, \dots, M - 1\}

Key K ──► [ Hash Function h(K) ] ──► Bucket Address (0 to M-1) ──► Physical Disk Block

Collision Resolution in Static Storage​

When two distinct keys produce the same bucket address (h(K1)=h(K2)h(K_1) = h(K_2)), a collision occurs. If the targeted disk block is already full:

  1. Open Addressing (Linear Probing): The engine searches sequentially for the next available slot in subsequent physical blocks. However, this disrupts locality and severely degrades performance on secondary disk storage.
  2. Overflow Chaining (Closed Addressing): The full primary bucket allocates an overflow block linked via a pointer, forming a linked list of overflow pages.
Primary Bucket 2: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Key: 102 β”‚ Key: 242 β”‚ NextPtr ┼──► Overflow Block: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Key: 382 β”‚ NULL β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The Three Fatal Flaws of Static Hashing​

  • Bucket Overflow Chains: As data accumulates, overflow chains grow long. Searching for an un-indexed key requires traversing multiple chained disk blocks, degrading O(1)O(1) access down to O(k)O(k) sequential disk I/Os.
  • Catastrophic Reorganization Cost: If the database expands significantly, the only remediation is to allocate a larger bucket array (e.g. 2M2M buckets) and recompute h(K)h(K) for every record in the tableβ€”halting production operations for hours.
  • Storage Fragmentation: If initial allocation is oversized to prevent collisions, empty or underutilized blocks waste large amounts of expensive disk space.

2. Extendible Hashing Architecture​

Definition: Extendible Hashing is a dynamic hashing technique that decouples the hashing directory from physical disk buckets, allowing individual buckets to split independently while the directory grows dynamically via doubling.

Core Structural Components​

  1. The Directory:
    • An array of 2d2^d pointers residing in memory.
    • dd is the Global Depth: it denotes the number of bits of the hash value currently used to index into the directory.
    • The directory contains 2d2^d entries numbered from 00 to 2dβˆ’12^d - 1 in binary.
  2. The Buckets:
    • Physical disk blocks storing data records.
    • Each individual bucket has a Local Depth (dβ€²d').
    • dβ€²d' denotes the number of hash bits that all keys residing inside that specific bucket have in common.

The Golden Invariant of Extendible Hashing: d′≀dforΒ everyΒ bucketd' \le d \quad \text{for every bucket}

The number of distinct directory entries pointing to a bucket with local depth dβ€²d' is: DirectoryΒ PointersΒ toΒ Bucket=2dβˆ’dβ€²\text{Directory Pointers to Bucket} = 2^{d - d'}

Global Depth d = 2
Directory: Buckets:
β”Œβ”€β”€β”€β”€β”€β”€β”
β”‚ 00 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί Bucket A (Local Depth d' = 2): Keys starting with 00
β”œβ”€β”€β”€β”€β”€β”€β”€
β”‚ 01 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί Bucket B (Local Depth d' = 2): Keys starting with 01
β”œβ”€β”€β”€β”€β”€β”€β”€
β”‚ 10 β”œβ”€β”€β”€β”
β”œβ”€β”€β”€β”€β”€β”€β”€ └─────────► Bucket C (Local Depth d' = 1): Keys starting with 1
β”‚ 11 β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”˜

(Notice that Bucket C has dβ€²=1d' = 1. Since dβˆ’dβ€²=2βˆ’1=1d - d' = 2 - 1 = 1, exactly 21=22^1 = 2 directory entries (10 and 11) point to Bucket C).


3. Step-by-Step Extendible Hashing Insertion Protocol​

To insert a record with search key KK:

  1. Calculate the hash value h(K)h(K) in binary.
  2. Examine the first dd bits of h(K)h(K) to locate the corresponding directory index.
  3. Follow the directory pointer to the physical bucket.
  4. If the bucket has free space: Insert the record.
  5. If the bucket is full (Overflow):
    • Check the overflowing bucket's local depth dβ€²d' against global depth dd:

Case A: If dβ€²<dd' < d (Local Depth is Less Than Global Depth)​

  • The directory does NOT double! Directory size remains 2d2^d.
  • Allocate a new empty bucket on disk.
  • Increment the local depth of both the old bucket and the new bucket: dnewβ€²=doldβ€²+1d'_{\text{new}} = d'_{\text{old}} + 1
  • Redistribute the records of the overflowing bucket between the old bucket and the new bucket based on the (dβ€²)th(d')^{\text{th}} bit.
  • Update directory pointers that previously pointed to the old bucket.

Case B: If dβ€²=dd' = d (Local Depth Equals Global Depth)​

  • Directory Doubling: The directory must double in size!
  • Increment the global depth: dnew=d+1d_{\text{new}} = d + 1
  • The directory doubles from 2d2^d entries to 2d+12^{d+1} entries. Each previous entry ii is split into two entries (2i2i and 2i+12i+1).
  • Allocate a new bucket and increment local depth: dnewβ€²=doldβ€²+1d'_{\text{new}} = d'_{\text{old}} + 1
  • Rehash and redistribute the records of the overflowing bucket using (d+1)(d+1) bits.
  • Update the directory pointers. All other un-split buckets now have two directory pointers pointing to them.

4. Step-by-Step Solved Problem: Extendible Hashing Insertion​

Problem Statement​

Assume a bucket capacity of 2 records. Initial global depth d=1d = 1 and local depth dβ€²=1d' = 1. Insert records whose 4-bit binary hash values are: K1=00012,K2=01002,K3=00102,K4=10102,K5=11002,K6=01112K_1 = 0001_2, \quad K_2 = 0100_2, \quad K_3 = 0010_2, \quad K_4 = 1010_2, \quad K_5 = 1100_2, \quad K_6 = 0111_2


Stepwise Execution Trace​

  1. Initial State (d=1d = 1):
    • Directory has 21=22^1 = 2 entries: [0] and [1].
    • Bucket A (dβ€²=1d'=1): points from 0.
    • Bucket B (dβ€²=1d'=1): points from 1.
  2. Insert K1(0001)K_1 (0001) and K2(0100)K_2 (0100):
    • First bit of both is 0. Both insert into Bucket A.
    • Bucket A is now full: [ 0001, 0100 ] (dβ€²=1d'=1).
  3. Insert K3(0010)K_3 (0010):
    • First bit is 0. Bucket A is full!
    • Here, local depth dβ€²=1d' = 1 and global depth d=1d = 1 (dβ€²=dd' = d).
    • Directory Doubles! Global depth becomes d=2d = 2. Directory entries become 00, 01, 10, 11.
    • Bucket A splits into Bucket A0A_0 (dβ€²=2d'=2, keys starting with 00) and Bucket A1A_1 (dβ€²=2d'=2, keys starting with 01).
    • Redistribute keys:
      • 0001β†’0001 \rightarrow starts with 00 β†’\rightarrow Bucket A0A_0.
      • 0010β†’0010 \rightarrow starts with 00 β†’\rightarrow Bucket A0A_0.
      • 0100β†’0100 \rightarrow starts with 01 β†’\rightarrow Bucket A1A_1.
    • Directory pointers:
      • 00 β†’\rightarrow Bucket A0A_0 (dβ€²=2d'=2)
      • 01 β†’\rightarrow Bucket A1A_1 (dβ€²=2d'=2)
      • 10 β†’\rightarrow Bucket B (dβ€²=1d'=1)
      • 11 β†’\rightarrow Bucket B (dβ€²=1d'=1)
  4. Insert K4(1010)K_4 (1010) and K5(1100)K_5 (1100):
    • Both start with 1. Directory entries 10 and 11 point to Bucket B.
    • Both insert into Bucket B. Bucket B is now full: [ 1010, 1100 ] (dβ€²=1d'=1).
  5. Insert K6(0111)K_6 (0111):
    • First two bits are 01.
    • Directory 01 points to Bucket A1A_1, which currently has 11 record (0100).
    • Inserts directly into Bucket A1A_1 with zero splits! Bucket A1A_1 now holds [ 0100, 0111 ].

Conclusion: Only the overflowing bucket split. No global table scan or database-wide rehashing was performed!


5. Linear Hashing Architecture​

Definition: Linear Hashing is an alternative dynamic hashing algorithm that completely eliminates the in-memory directory.

Instead of doubling a directory, Linear Hashing splits buckets sequentially in linear round-robin order (0,1,2,…0, 1, 2, \dots) using a split pointer (ss) and a family of hash functions: hi(K)=Kβ€Šmodβ€Š(2iβ‹…N)h_i(K) = K \bmod (2^i \cdot N) hi+1(K)=Kβ€Šmodβ€Š(2i+1β‹…N)h_{i+1}(K) = K \bmod (2^{i+1} \cdot N) Where NN is the initial number of buckets and ii is the current round number.

Key Mechanism:

  • When any bucket overflows (creating an overflow block), the bucket pointed to by the split pointer ss is split, and ss advances: s=s+1s = s + 1
  • When ss reaches the end of the bucket range (2iβ‹…N2^i \cdot N), all buckets have doubled. Pointer ss resets to 00, and round number increments i=i+1i = i + 1.
  • Advantage: Zero directory memory overhead. Space allocation grows smoothly linearly rather than exponentially.

6. Architectural Comparison Matrix​

PropertyStatic HashingExtendible HashingLinear HashingB+ Tree
Directory Required?NoYes (Doubles in RAM)No (Direct computation)No (Tree structure)
Growth ModeStatic (Manual rehash)Dynamic (Doubling)Dynamic (Linear)Dynamic (Splits)
Point Query CostO(1)O(1) (worst O(k)O(k))Strictly O(1)O(1) (2 memory accesses)O(1)O(1)O(log⁑mN)O(\log_m N) (3–4 I/Os)
Range Query SupportNoneNoneNoneExcellent (O(k)O(k) sequential)
Space UtilizationPoor (spills/wastes)Moderate (β‰ˆ69%\approx 69\%)Moderate (β‰ˆ60%\approx 60\%)High (β‰ˆ70%\approx 70\%)

πŸ“ Architecture / Visual Blueprint​


🏭 In The Real World: Production Case Study​

PostgreSQL Hash Indexes & In-Memory Redis Dictionaries​

  1. PostgreSQL Hash Indexes (USING HASH):

    • Historically, PostgreSQL hash indices were not recommended because they were not Write-Ahead Logged (crash-unsafe).
    • In PostgreSQL 10+, hash indexes were completely re-architected with full WAL logging and concurrency control based on a variant of Linear Hashing.
    • For pure equality queries on large string keys (e.g. searching 64-character SHA-256 tokens: WHERE token = 'a8f4c...'), PostgreSQL hash indexes occupy significantly less disk space than B+ Trees while offering consistent single-hop lookups.
  2. Redis In-Memory Progressive Rehashing:

    • The open-source in-memory store Redis maintains two hash tables (ht[0] and ht[1]).
    • When the load factor exceeds capacity, Redis triggers Progressive Rehashing: instead of rehashing millions of keys in one blocking pause, Redis rehashes a small batch of keys during every client command, seamlessly migrating buckets in background time slices without latency spikes.

🎯 Exam & Interview Pitfall Check​

Core Conceptual Questions

Question 1: Under what exact condition does the directory double in Extendible Hashing?

Answer: The directory doubles if and only if an overflow occurs in a bucket whose Local Depth equals the Global Depth (dβ€²=dd' = d). If a bucket overflows when its local depth is strictly less than the global depth (dβ€²<dd' < d), the bucket splits into two new buckets, its local depth increments by 11, and existing directory pointers are redirectedβ€”without doubling the directory.


Question 2: Why are Hashing techniques virtually never used as primary clustered indices in relational databases?

Answer: Relational queries heavily rely on range scans, sorting, and inequalities (WHERE age BETWEEN 25 AND 35, ORDER BY timestamp DESC, LIMIT 10). Hash functions uniformly randomize key distributions across buckets to eliminate collisions, completely destroying lexicographical and numerical ordering. As a result, range queries on a hash index degenerate into a full scan of all buckets, making B+ Trees the universal default for relational storage engines.

Common Interview Traps

Trap 1: Assuming that a directory doubling splits all buckets in the database. When the directory doubles from 2d2^d to 2d+12^{d+1}, only the single overflowing bucket is split! All other buckets remain untouched on disk; the new directory simply creates two duplicate pointer entries for each of the un-split buckets.

Trap 2: Confusing the number of directory pointers to a bucket. A bucket with local depth dβ€²d' in an Extendible Hashing scheme with global depth dd is pointed to by exactly 2dβˆ’dβ€²2^{d - d'} directory entries. If d=4d = 4 and dβ€²=2d' = 2, exactly 24βˆ’2=42^{4 - 2} = 4 directory entries point to that single physical bucket.


πŸ’¬

Discussion & Doubts