#1 India's Top IT Training Institute

×

About Uncodemy

Know more about who we are and what we stand for.

About Us

About Us

Discover our mission to equip individuals with essential IT skills and industry expertise.

Read More →
UnCodeMy Gallery

UnCodeMy Gallery

Explore highlights from our IT training sessions and success stories.

Read More →
×

Free Resources

Learn, practice and plan your career — bilkul free.

Tutorials

Tutorials

Step-by-step written tutorials to learn any technology at your own pace.

Read More →
Free Counselling

Free Counselling

Book a free video counselling session and pick the right course for you.

Read More →
Online Compiler

Online Compiler

Write, run and test your code online — no setup required.

Read More →
Hashing

Hash Table in Data Structures

A Hash Table (also called a Hash Map) is a data structure that implements an associative array — a structure that maps keys to values. It uses a hash function to compute an index into an array of buckets or slots, from which the desired value can be found. Hash tables offer O(1) average-case time complexity for search, insertion, and deletion.

Components of a Hash Table

  • Keys: Unique identifiers used to store and retrieve values.
  • Values: The data associated with a key.
  • Hash Function: Maps a key to an index in the array.
  • Buckets / Slots: The array where key-value pairs are stored.
  • Collision Resolution Strategy: Handles cases where two keys map to the same index.

How a Hash Table Works

  1. Insert: Compute the hash of the key → get an index → store the key-value pair at that index (handling collisions if needed).
  2. Search: Compute the hash of the key → get an index → retrieve the value at that index (or traverse the chain/list to find the key).
  3. Delete: Compute the hash of the key → get an index → remove the key-value pair from that index.

Collision Resolution Strategies

1. Separate Chaining

Each bucket contains a linked list (or another data structure) of entries that hash to the same index.

class HashTableChaining:
    def __init__(self, size=10):
        self.size = size
        self.table = [[] for _ in range(size)]

    def _hash(self, key):
        return hash(key) % self.size

    def insert(self, key, value):
        idx = self._hash(key)
        for i, (k, v) in enumerate(self.table[idx]):
            if k == key:
                self.table[idx][i] = (key, value)
                return
        self.table[idx].append((key, value))

    def search(self, key):
        idx = self._hash(key)
        for k, v in self.table[idx]:
            if k == key:
                return v
        return None

    def delete(self, key):
        idx = self._hash(key)
        for i, (k, v) in enumerate(self.table[idx]):
            if k == key:
                self.table[idx].pop(i)
                return True
        return False

2. Open Addressing

All entries are stored in the array itself. When a collision occurs, the algorithm probes for the next available slot.

a. Linear Probing

Checks the next slot sequentially: h(key, i) = (h(key) + i) % size.

class HashTableLinearProbing:
    def __init__(self, size=10):
        self.size = size
        self.keys = [None] * size
        self.values = [None] * size

    def _hash(self, key):
        return hash(key) % self.size

    def insert(self, key, value):
        idx = self._hash(key)
        i = 0
        while i < self.size:
            probe = (idx + i) % self.size
            if self.keys[probe] is None or self.keys[probe] == key:
                self.keys[probe] = key
                self.values[probe] = value
                return
            i += 1
        raise Exception("Hash table is full")

    def search(self, key):
        idx = self._hash(key)
        i = 0
        while i < self.size:
            probe = (idx + i) % self.size
            if self.keys[probe] is None:
                return None
            if self.keys[probe] == key:
                return self.values[probe]
            i += 1
        return None

    def delete(self, key):
        idx = self._hash(key)
        i = 0
        while i < self.size:
            probe = (idx + i) % self.size
            if self.keys[probe] is None:
                return False
            if self.keys[probe] == key:
                self.keys[probe] = None
                self.values[probe] = None
                # Note: Use tombstone for better performance in practice
                return True
            i += 1
        return False

b. Quadratic Probing

Uses a quadratic function to find the next slot: h(key, i) = (h(key) + c₁*i + c₂*i²) % size.

c. Double Hashing

Uses a second hash function to determine the step size: h(key, i) = (h₁(key) + i * h₂(key)) % size.

Load Factor and Rehashing

  • Load Factor (α): The ratio of the number of entries to the table size: α = n / m where n is the number of entries and m is the table size.
  • Rehashing: When the load factor exceeds a threshold (typically 0.75), the table is resized (usually doubled) and all entries are re-inserted. This maintains O(1) performance.
def rehash(self):
    old_keys = self.keys
    old_values = self.values
    self.size *= 2
    self.keys = [None] * self.size
    self.values = [None] * self.size
    for key, value in zip(old_keys, old_values):
        if key is not None:
            self.insert(key, value)

Implementation in Python

Using Built-in dict

# Python dictionary (hash table)
hash_table = {}

# Insert
hash_table["apple"] = 10
hash_table["banana"] = 20
hash_table["orange"] = 30

# Search
print(hash_table.get("banana"))  # 20
print(hash_table.get("grape", "Not found"))  # Not found

# Update
hash_table["apple"] = 15

# Delete
del hash_table["orange"]

# Iterate
for key, value in hash_table.items():
    print(f"{key}: {value}")

# Membership
if "apple" in hash_table:
    print("Apple found!")

Custom Implementation with Chaining

class HashTable:
    def __init__(self, size=10):
        self.size = size
        self.table = [[] for _ in range(size)]
        self.count = 0

    def _hash(self, key):
        return hash(key) % self.size

    def insert(self, key, value):
        idx = self._hash(key)
        for i, (k, v) in enumerate(self.table[idx]):
            if k == key:
                self.table[idx][i] = (key, value)
                return
        self.table[idx].append((key, value))
        self.count += 1

        # Rehash if load factor > 0.75
        if self.count / self.size > 0.75:
            self._rehash()

    def search(self, key):
        idx = self._hash(key)
        for k, v in self.table[idx]:
            if k == key:
                return v
        return None

    def delete(self, key):
        idx = self._hash(key)
        for i, (k, v) in enumerate(self.table[idx]):
            if k == key:
                self.table[idx].pop(i)
                self.count -= 1
                return True
        return False

    def _rehash(self):
        old_table = self.table
        self.size *= 2
        self.table = [[] for _ in range(self.size)]
        self.count = 0
        for bucket in old_table:
            for key, value in bucket:
                self.insert(key, value)

    def __str__(self):
        result = []
        for i, bucket in enumerate(self.table):
            if bucket:
                result.append(f"{i}: {bucket}")
        return "\n".join(result)

# Example usage
ht = HashTable(5)
ht.insert("apple", 10)
ht.insert("banana", 20)
ht.insert("orange", 30)
ht.insert("grape", 40)
print(ht.search("banana"))  # 20
print(ht)

Implementation in Java

Using Built-in HashMap

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // HashMap
        HashMap map = new HashMap<>();

        // Insert
        map.put("apple", 10);
        map.put("banana", 20);
        map.put("orange", 30);

        // Search
        System.out.println(map.get("banana"));  // 20
        System.out.println(map.getOrDefault("grape", -1));  // -1

        // Update
        map.put("apple", 15);

        // Delete
        map.remove("orange");

        // Iterate
        for (Map.Entry entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // Membership
        if (map.containsKey("apple")) {
            System.out.println("Apple found!");
        }
    }
}

Custom Implementation

import java.util.LinkedList;

class HashTableCustom {
    private class Entry {
        String key;
        int value;
        Entry(String key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private LinkedList[] table;
    private int size;
    private int count;

    @SuppressWarnings("unchecked")
    public HashTableCustom(int size) {
        this.size = size;
        this.count = 0;
        table = new LinkedList[size];
        for (int i = 0; i < size; i++) {
            table[i] = new LinkedList<>();
        }
    }

    private int hash(String key) {
        return Math.abs(key.hashCode()) % size;
    }

    public void insert(String key, int value) {
        int idx = hash(key);
        for (Entry entry : table[idx]) {
            if (entry.key.equals(key)) {
                entry.value = value;
                return;
            }
        }
        table[idx].add(new Entry(key, value));
        count++;
        if ((double) count / size > 0.75) {
            rehash();
        }
    }

    public Integer search(String key) {
        int idx = hash(key);
        for (Entry entry : table[idx]) {
            if (entry.key.equals(key)) {
                return entry.value;
            }
        }
        return null;
    }

    public boolean delete(String key) {
        int idx = hash(key);
        for (Entry entry : table[idx]) {
            if (entry.key.equals(key)) {
                table[idx].remove(entry);
                count--;
                return true;
            }
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    private void rehash() {
        LinkedList[] oldTable = table;
        size *= 2;
        table = new LinkedList[size];
        for (int i = 0; i < size; i++) {
            table[i] = new LinkedList<>();
        }
        count = 0;
        for (LinkedList bucket : oldTable) {
            for (Entry entry : bucket) {
                insert(entry.key, entry.value);
            }
        }
    }
}

Implementation in C++

Using STL unordered_map

#include 
#include 
using namespace std;

int main() {
    unordered_map map;

    // Insert
    map["apple"] = 10;
    map["banana"] = 20;
    map["orange"] = 30;

    // Search
    cout << map["banana"] << endl;  // 20

    // Update
    map["apple"] = 15;

    // Delete
    map.erase("orange");

    // Iterate
    for (auto& pair : map) {
        cout << pair.first << ": " << pair.second << endl;
    }

    // Membership
    if (map.find("apple") != map.end()) {
        cout << "Apple found!" << endl;
    }

    return 0;
}

Custom Implementation

#include 
#include 
#include 
#include 
using namespace std;

class HashTable {
private:
    struct Entry {
        string key;
        int value;
        Entry(string k, int v) : key(k), value(v) {}
    };

    vector> table;
    int size;
    int count;

    int hashFunction(const string& key) {
        int hash = 0;
        for (char c : key) {
            hash = (hash * 31 + c) % size;
        }
        return hash;
    }

    void rehash() {
        vector> oldTable = table;
        size *= 2;
        table.clear();
        table.resize(size);
        count = 0;
        for (auto& bucket : oldTable) {
            for (auto& entry : bucket) {
                insert(entry.key, entry.value);
            }
        }
    }

public:
    HashTable(int s = 10) : size(s), count(0) {
        table.resize(size);
    }

    void insert(const string& key, int value) {
        int idx = hashFunction(key);
        for (auto& entry : table[idx]) {
            if (entry.key == key) {
                entry.value = value;
                return;
            }
        }
        table[idx].push_back(Entry(key, value));
        count++;
        if ((double)count / size > 0.75) {
            rehash();
        }
    }

    int* search(const string& key) {
        int idx = hashFunction(key);
        for (auto& entry : table[idx]) {
            if (entry.key == key) {
                return &entry.value;
            }
        }
        return nullptr;
    }

    bool remove(const string& key) {
        int idx = hashFunction(key);
        for (auto it = table[idx].begin(); it != table[idx].end(); ++it) {
            if (it->key == key) {
                table[idx].erase(it);
                count--;
                return true;
            }
        }
        return false;
    }
};

Complexity Analysis

OperationAverage CaseWorst Case
SearchO(1)O(n)
InsertO(1)O(n)
DeleteO(1)O(n)
SpaceO(n)O(n)

Note: Worst-case occurs when all keys collide (poor hash function or adversarial input).

Advantages & Disadvantages

Advantages

  • Fast Operations: O(1) average-time for search, insert, and delete.
  • Flexible Keys: Can use any data type as a key (with proper hashing).
  • Dynamic: Can grow/shrink with rehashing.
  • Widely Supported: Built into most programming languages.

Disadvantages

  • Collisions: Can degrade performance to O(n) in worst case.
  • Memory Overhead: May use more memory than other structures.
  • Unordered: Does not preserve insertion order (unless using LinkedHashMap).
  • Hash Function Quality: Performance depends heavily on the quality of the hash function.

Applications

  • Databases: Hash-based indexing for fast lookups.
  • Caches: LRU caches, web caches, DNS caching.
  • Symbol Tables: Compilers and interpreters store variables.
  • Programming Language Implementations: Python dict, Java HashMap, C++ unordered_map.
  • Sets: Removing duplicates, membership testing.
  • Distributed Systems: Consistent hashing for load balancing.
  • Password Storage: Storing hashed passwords.

Common Hash Table Problems

  • Two Sum: Find two numbers that sum to target.
  • Top K Frequent Elements: Find the k most frequent elements.
  • Group Anagrams: Group strings that are anagrams of each other.
  • Longest Substring Without Repeating Characters: Use hash map to track character positions.
  • Word Pattern: Check if a pattern matches a string.
  • LRU Cache: Implement a Least Recently Used cache using a hash map + doubly linked list.

Key Takeaways

  • A hash table is a key-value store that provides O(1) average-case operations.
  • It uses a hash function to map keys to array indices.
  • Collisions are resolved using chaining (linked lists) or open addressing (linear/quadratic probing, double hashing).
  • The load factor determines when rehashing is needed to maintain performance.
  • Built-in implementations: Python's dict, Java's HashMap, C++'s unordered_map.
  • Hash tables are used in databases, caches, compilers, and distributed systems.

Ready to master Data Structures & Algorithms?

Learn DSA hands-on with mentor-led sessions, real interview practice, and placement support.

Explore Course