Introduction
Heap files and sorted files both make the DBMS hunt for a record: a linear scan costs
Definition
A hash file organization stores each record at an address derived from one of its fields by a hash function
The payoff is that an equality search on the hash field — WHERE ssn = '123456789' — takes roughly one block access, independent of file size. The price is that hashing provides no help for anything else: no ordered retrieval, no range queries, no searching on other fields.
| Organization | Equality search on the field | Ordered / range access |
|---|---|---|
| Heap | none (must sort) | |
| Sorted (ordering field) | excellent | |
| Hashed (hash field) | ~1 | none |
Internal Hashing
Internal hashing is hashing applied to a table held in main memory, and it is the model everything else builds on. The table is an array of
The workhorse function for numeric keys is the division-remainder method:
Choosing
Non-numeric keys must first be turned into an integer. Two common tricks:
- Character arithmetic — sum the numeric codes of the characters, then take
. - Folding — split the key into pieces of equal length, then add (or XOR) the pieces together before applying
. Folding uses all of the key, so keys sharing a prefix or suffix still land apart.
Collisions
A hash function maps a large key space onto a small address space, so two distinct keys will eventually hash to the same slot. That is a collision, and the procedure for placing the second record is collision resolution.
Collisions are not a rare accident to be engineered away — they are guaranteed, and the quality of a hash organization is mostly the quality of its collision handling.
Collision Resolution
| Method | How it works | Cost / behaviour |
|---|---|---|
| Open addressing | On collision, probe the following slots ( | No extra space, but colliding records cluster together, and one cluster lengthens the search for unrelated keys |
| Chaining | Each slot holds a pointer to a linked list of overflow records kept in an extra area beyond the | Search follows one pointer chain; deletion is easy; needs pointer space |
| Multiple hashing | On collision, apply a second hash function | Avoids clustering better than plain probing, at the cost of extra computation |
Keep the table from filling up
Hashing degrades sharply as the table fills. The load factor is
Performance stays close to one access while
Deletion under open addressing is awkward for the same reason: physically removing a record breaks the probe chain of records placed after it, so deleted slots are marked rather than emptied — the same deletion marker idea used in heap files.
External Hashing for Disk Files
Moving to disk changes one thing fundamentally: the unit of transfer is a block, not a record. Hashing to individual record addresses would waste an entire block access per record and make collisions catastrophic. So disk hashing hashes to buckets.
Definition
A bucket is either one disk block or a small cluster of contiguous blocks. The hash function maps a key to a bucket number, and a bucket-address table converts that bucket number into the actual disk block address.
This indirection matters: it lets the file's blocks be relocated on disk without changing the hash function.
Because a bucket holds many records — bfr of them — a collision is only a problem when the bucket is full. Several keys hashing to the same bucket is the normal, desirable case.
Overflow handling
When a bucket fills, the extra records go into an overflow area, and each bucket keeps a pointer to a linked list (chain) of its own overflow records. Chaining is the standard choice on disk, since following a pointer costs one block access while probing neighbouring buckets would scatter reads across the file.

Retrieval cost is therefore:
which stays near 1 block access as long as buckets are not overloaded — and that is the entire design goal.
Operations
- Insert — hash to the bucket, read it, add the record if there is room, write it back. If full, append to the overflow chain.
- Delete — locate the record, remove it, and if a record is available in the overflow chain, move it up into the main bucket so future searches stay short.
- Modify — changing a non-hash field is a read/write in place. Changing the hash field relocates the record, so it is implemented as a delete followed by an insert.
The problem with static hashing
Everything above is static hashing:
Why
- Too small an
— buckets overflow, chains grow, and the one-access guarantee is lost. - Too large an
— most buckets sit mostly empty, wasting a great deal of disk. - Fixing it means rehashing — choosing a new
and redistributing every record in the file, since the addresses all change. For a large file this is a full offline reorganization.
The techniques in the next section exist to make a hash file grow and shrink gracefully, without ever rehashing the whole thing.
Dynamic File Expansion
All three schemes below share the same core idea, so it is worth stating once.
Instead of using
Using 0 and those whose next bit is 1. So the file can be expanded one bucket at a time, touching only the records in the bucket being split. No global rehash.
The three techniques differ in how they keep track of which buckets have been split.
Extendible Hashing
Extendible hashing adds a level of indirection: a directory — an array of
To find a record, take the first
Two directory entries may point to the same bucket. Each bucket therefore records its own local depth

Splitting a full bucket with local depth
- Distribute its records into two buckets using bit
; both new buckets get local depth . - If
, the directory already has enough entries — just repoint the affected half at the new bucket. The directory does not change size. - If
, there are no spare bits: double the directory ( ), copying each old pointer into the two new entries that correspond to it, then repoint as in step 2.
Deletion works in reverse: when two buddy buckets (same local depth, hash values differing only in the last bit) become empty enough, they are merged and
Cost
Retrieval is two block accesses — one for the directory entry, one for the bucket — and often one, because the directory is small enough to stay in main memory. Doubling the directory is cheap since it only copies pointers, not records.
The directory is the weak point
The directory must be maintained, and it doubles in size the moment a single bucket at maximum depth overflows. With a badly skewed key distribution the directory can grow far larger than the data warrants.
Dynamic Hashing
Dynamic hashing is the same splitting idea with the flat directory replaced by a binary trie: internal nodes have a 0 child and a 1 child, and the leaves point to buckets. Searching means walking down the tree consuming one bit of

Splitting a bucket simply turns its leaf into an internal node with two new leaves — a purely local change. There is no doubling step, so a skewed distribution grows only the branches it actually uses rather than the whole directory.
The trade-off is that traversing a tree costs more than indexing an array, and the tree itself needs pointer space and maintenance. In practice extendible and linear hashing are the ones that get implemented.
Linear Hashing
Linear hashing is the most elegant of the three: it allows the file to grow and shrink with no directory at all.
Extendible and dynamic hashing both need a structure — an array, a trie — whose job is to remember which buckets have already been split. Linear hashing removes the need for one by giving up the freedom to choose: buckets are split in a fixed order,
Setup
The file starts with
— the split pointer, the number of the next bucket to be split. It starts at . - a second hash function
.
The search rule
Read it as a single question: has my bucket been split yet? Buckets
Why a record can never end up lost
Because
So splitting bucket
Splitting
The counter-intuitive part: when a bucket overflows, the bucket that gets split is not the one that overflowed — it is bucket
Each split:
- Appends a new bucket at the end of the file, which is bucket
. - Redistributes the records of bucket
— including its overflow chain — between and using . - Increments
.
A worked trace
Take
- Insert 17.
, and , so it belongs in — which is full, so 17 goes to 's overflow chain. The overflow triggers a split of bucket , not bucket 1. Bucket 0's records go through : , . Result: , new , and . - Insert 21.
, and , so again — still full, so 21 joins the chain. This time the split does land on bucket 1, because . Its four records plus the chain are redistributed by : , , , . Result: , new , the chain is gone, and .
Searching now, with
Completing a round
When
so the file doubles once per round while the search rule above never changes.
Controlling splits with the load factor
Splitting on every overflow works, but it keeps the file only about 60% full — a lot of wasted space. Implementations therefore drive splitting from the file load factor instead:
where
Contraction
Contraction is the mirror image, and it is what keeps the load factor from collapsing when records are deleted. When
Why linear hashing wins in practice
- It keeps the load factor fairly constant while the file grows and shrinks, because splits and merges are both driven by the same measurement.
- It needs no directory — just the counter
and the current — so there is nothing to double, nothing to traverse, and nothing extra to keep in memory.
The price is that the bucket being split is chosen by position, not by need: a bucket may be split while nearly empty, and an overloaded bucket must wait behind an overflow chain until
Comparison and When to Use Hashing
| Static hashing | Extendible | Dynamic | Linear | |
|---|---|---|---|---|
| Directory structure | bucket-address table (fixed) | array of | binary trie | none |
| Grows without full rehash | ✗ | ✓ | ✓ | ✓ |
| Splits which bucket | — | the one that overflowed | the one that overflowed | bucket |
| Typical retrieval cost | 1 + overflow chain | 1–2 | 1 + trie traversal | 1 + overflow chain |
| Main drawback | overflow chains grow; rehashing is offline | directory can double abruptly | tree maintenance cost | overflow chains on unsplit buckets |
What hashing cannot do
Hashing scatters records deliberately, so a good hash function destroys any relationship between key order and physical position. Consequently:
- Range queries (
WHERE age BETWEEN 20 AND 30) require reading the entire file. - Ordered retrieval requires a full external sort.
- Searching on any field other than the hash field is a linear scan, exactly as in a heap file.
- The hash field is usually forced to be a key; hashing on a field with few distinct values piles everything into a handful of buckets.
So the choice between the organizations is really a choice about the access pattern:
| Dominant access pattern | Best organization |
|---|---|
| Equality lookup on one key field | Hashing |
| Range and ordered access | Sorted file + index, or a B⁺-tree |
| Bulk load, full scans, no lookups | Heap file |
This is also why real systems rarely rely on a hash organization alone. What they do instead is keep the data in some primary organization and build separate access structures — indexes — over the fields that queries actually use, including hash indexes for equality and B⁺-tree indexes for ranges. That is the subject of the indexing chapter.