DEV Community

Cover image for Hash Maps in C++ Made Simple πŸš€
Rehan shaikh
Rehan shaikh

Posted on

Hash Maps in C++ Made Simple πŸš€

Click to open github consisting code file

1. What is a Hash Map?

A Hash Map is a data structure that stores data as key–value pairs.

Key  β†’  Value

"apple" β†’ 5
"cat"   β†’ 3
"dog"   β†’ 7
Enter fullscreen mode Exit fullscreen mode

Instead of searching through all elements, a hash map uses a hash function to quickly locate the position where a key-value pair should be stored.

Main idea:

Example:

mp["apple"] = 50


Key
"apple"
   ↓
Hash Function
hash("apple")
   ↓
Hash Code
983746          ← Code generated by Hash Function
   ↓
Compression
983746 % 10
   ↓
Bucket Index
6
   ↓
Bucket
bucket[6]
   ↓
Key-Value Pair
("apple", 50)
   ↓
Value
50
Enter fullscreen mode Exit fullscreen mode

2. Why Do We Use Hash Maps?

Hash Maps are useful when we need to:

  • Count frequencies
  • Search for data quickly
  • Store relationships between keys and values
  • Detect duplicates
  • Cache data
  • Map complex keys such as strings to values

Example: Character Frequency

string s = "banana";

unordered_map<char, int> mp;

for(char ch : s) {
    mp[ch]++;
}
Enter fullscreen mode Exit fullscreen mode

Result:

b β†’ 1
a β†’ 3
n β†’ 2
Enter fullscreen mode Exit fullscreen mode

Without a hash map, you might need to manually calculate array indexes:

freq[ch - 'a']++;
Enter fullscreen mode Exit fullscreen mode

That works for lowercase English letters, but hash maps are more flexible when keys are words, strings, IDs, etc.

"apple"  β†’ 10
"banana" β†’ 5
"mango"  β†’ 8
Enter fullscreen mode Exit fullscreen mode

3. map vs unordered_map

Feature unordered_map map
Internal structure Hash Table Balanced BST
Average search O(1) O(log N)
Insertion O(1) average O(log N)
Deletion O(1) average O(log N)
Order No guaranteed order Sorted by key
Worst case O(N) O(log N)

Use unordered_map when:

  • You need fast lookup.
  • Order does not matter.
  • You are doing frequency counting.

Use map when:

  • Keys must remain sorted.
  • You need ordered traversal.
  • You need predictable O(log N) performance.

4. Creating a Hash Map

Unordered Map

unordered_map<string, int> mp;
Enter fullscreen mode Exit fullscreen mode

Ordered Map

map<string, int> mp;
Enter fullscreen mode Exit fullscreen mode

Example:

unordered_map<string, int> marks;

marks["Rehan"] = 90;
marks["Amit"] = 85;
marks["Rahul"] = 75;
Enter fullscreen mode Exit fullscreen mode

5. Insertion and Updating

Method 1: Using []

mp["apple"] = 10;
Enter fullscreen mode Exit fullscreen mode

If "apple" already exists:

Value gets updated.
Enter fullscreen mode Exit fullscreen mode

If it does not exist:

A new key-value pair is created.
Enter fullscreen mode Exit fullscreen mode

Example:

mp["apple"] = 10;
mp["apple"] = 20;
Enter fullscreen mode Exit fullscreen mode

Final result:

apple β†’ 20
Enter fullscreen mode Exit fullscreen mode

Method 2: Using insert()

mp.insert({"apple", 10});
Enter fullscreen mode Exit fullscreen mode

or:

mp.insert(make_pair("apple", 10));
Enter fullscreen mode Exit fullscreen mode

6. Accessing Elements

Using []

cout << mp["apple"];
Enter fullscreen mode Exit fullscreen mode

⚠️ Important:

If the key does not exist:

mp["banana"];
Enter fullscreen mode Exit fullscreen mode

C++ creates the key with a default value.

For:

unordered_map<string, int> mp;
Enter fullscreen mode Exit fullscreen mode
mp["banana"] = 0
Enter fullscreen mode Exit fullscreen mode

So after:

cout << mp["banana"];
Enter fullscreen mode Exit fullscreen mode

The map becomes:

banana β†’ 0
Enter fullscreen mode Exit fullscreen mode

Using .at()

cout << mp.at("apple");
Enter fullscreen mode Exit fullscreen mode

If the key does not exist:

Throws an out_of_range exception.
Enter fullscreen mode Exit fullscreen mode

Therefore:

mp[key]     β†’ May create a new key
mp.at(key)  β†’ Does NOT create a new key
Enter fullscreen mode Exit fullscreen mode

7. Checking Whether a Key Exists

Using count()

if(mp.count("apple")) {
    cout << "Found";
}
Enter fullscreen mode Exit fullscreen mode

For unique-key maps:

1 β†’ Key exists
0 β†’ Key does not exist
Enter fullscreen mode Exit fullscreen mode

Using find()

if(mp.find("apple") != mp.end()) {
    cout << "Found";
}
Enter fullscreen mode Exit fullscreen mode

If found:

auto it = mp.find("apple");

cout << it->first;   // Key
cout << it->second;  // Value
Enter fullscreen mode Exit fullscreen mode

8. Deletion

Delete using a key:

mp.erase("apple");
Enter fullscreen mode Exit fullscreen mode

Example:

Before:

apple  β†’ 10
banana β†’ 20
Enter fullscreen mode Exit fullscreen mode

After:

mp.erase("apple");
Enter fullscreen mode Exit fullscreen mode

Result:

banana β†’ 20
Enter fullscreen mode Exit fullscreen mode

9. Size and Empty Check

mp.size();
Enter fullscreen mode Exit fullscreen mode

Returns the number of key-value pairs.

mp.empty();
Enter fullscreen mode Exit fullscreen mode

Returns:

true  β†’ Map is empty
false β†’ Map contains elements
Enter fullscreen mode Exit fullscreen mode

10. Iterating Through a Map

Range-Based Loop

for(auto x : mp) {
    cout << x.first << " " << x.second << endl;
}
Enter fullscreen mode Exit fullscreen mode

Why .first and .second?

Each element inside a map is stored as a pair:

pair<Key, Value>
Enter fullscreen mode Exit fullscreen mode

Therefore:

x.first
Enter fullscreen mode Exit fullscreen mode

means:

Key
Enter fullscreen mode Exit fullscreen mode

and:

x.second
Enter fullscreen mode Exit fullscreen mode

means:

Value
Enter fullscreen mode Exit fullscreen mode

Example:

unordered_map<string, int> mp;

mp["Apple"] = 10;
mp["Banana"] = 20;
Enter fullscreen mode Exit fullscreen mode

Conceptually:

x = {"Apple", 10}

x.first  β†’ "Apple"
x.second β†’ 10
Enter fullscreen mode Exit fullscreen mode

11. Hash Function

A hash function converts a key into a numeric value.

Example:

"apple"
   ↓
Hash Function
   ↓
123456
Enter fullscreen mode Exit fullscreen mode

For an integer key:

25 β†’ hash β†’ 25
Enter fullscreen mode Exit fullscreen mode

For a string:

"hello" β†’ hash β†’ some large integer
Enter fullscreen mode Exit fullscreen mode

The exact hash calculation is handled internally by C++.


12. Compression Function

The hash code can be very large.

Example:

Hash Code = 123456789
Enter fullscreen mode Exit fullscreen mode

But if the bucket array has only 10 buckets, we need an index between:

0 to 9
Enter fullscreen mode Exit fullscreen mode

A compression function can do:

hashCode % bucketSize
Enter fullscreen mode Exit fullscreen mode

Example:

123456789 % 10 = 9
Enter fullscreen mode Exit fullscreen mode

Therefore:

Bucket Index = 9
Enter fullscreen mode Exit fullscreen mode

13. Bucket Array

Internally, a hash map has an array of buckets.

Example:

Index

0 β†’ [ ]
1 β†’ [ Key-Value ]
2 β†’ [ ]
3 β†’ [ Key-Value ]
4 β†’ [ Key-Value ]
5 β†’ [ ]
Enter fullscreen mode Exit fullscreen mode

The hash function decides which bucket should store a particular key.


14. Hash Collision

A collision happens when two different keys are assigned to the same bucket.

Example:

"apple"  β†’ Index 3
"banana" β†’ Index 3
Enter fullscreen mode Exit fullscreen mode

Both cannot directly occupy the same single position.

So we need collision resolution techniques.


15. Collision Resolution Techniques

A. Separate Chaining

Also commonly called:

Open Hashing
Closed Addressing
Enter fullscreen mode Exit fullscreen mode

Each bucket stores multiple elements, traditionally using a linked list.

Bucket 0 β†’ NULL

Bucket 1 β†’ ("apple", 10)
            ↓
            ("banana", 20)
            ↓
            ("mango", 30)
Enter fullscreen mode Exit fullscreen mode

Advantage

Multiple elements can exist in the same bucket.


B. Open Addressing

Also called:

Closed Hashing
Open Addressing
Enter fullscreen mode Exit fullscreen mode

All elements are stored directly inside the table.

When a collision occurs:

Find another available position.
Enter fullscreen mode Exit fullscreen mode

Example:

Index 3 β†’ Occupied
Index 4 β†’ Empty

Store element at Index 4
Enter fullscreen mode Exit fullscreen mode

Linear Probing

Search sequentially:

h(k)
h(k) + 1
h(k) + 2
h(k) + 3
Enter fullscreen mode Exit fullscreen mode

Quadratic Probing

Search using quadratic jumps:

h(k) + 1Β²
h(k) + 2Β²
h(k) + 3Β²
Enter fullscreen mode Exit fullscreen mode

Example:

Original Index = 5

5 + 1Β² = 6
5 + 2Β² = 9
5 + 3Β² = 14
Enter fullscreen mode Exit fullscreen mode

Double Hashing

Uses a second hash function to calculate the jump.

Conceptually:

New Position =
h1(key) + i Γ— h2(key)
Enter fullscreen mode Exit fullscreen mode

This generally distributes collisions better than simple linear probing.


16. Load Factor

The load factor tells us how full the hash table is.

Formula

Load Factor = Number of Elements / Number of Buckets
Enter fullscreen mode Exit fullscreen mode

or:

Load Factor = N / B
Enter fullscreen mode Exit fullscreen mode

Example:

Elements = 8
Buckets  = 10

Load Factor = 8 / 10 = 0.8
Enter fullscreen mode Exit fullscreen mode

Why is Load Factor Important?

If too many elements are stored:

More collisions
        ↓
Slower operations
Enter fullscreen mode Exit fullscreen mode

When the load factor becomes too high, the hash table may perform rehashing.


17. Rehashing

When the hash table becomes too full:

More Buckets are Created
        ↓
All Elements are Recalculated
        ↓
Elements are Redistributed
Enter fullscreen mode Exit fullscreen mode

Example:

Before:

10 Buckets
8 Elements
Load Factor = 0.8
Enter fullscreen mode Exit fullscreen mode

After rehashing:

20 Buckets
8 Elements
Load Factor = 0.4
Enter fullscreen mode Exit fullscreen mode

This helps maintain efficient average-case performance.


18. Time Complexity

Operation unordered_map Average unordered_map Worst map
Insert O(1) O(N) O(log N)
Search O(1) O(N) O(log N)
Delete O(1) O(N) O(log N)
Access O(1) O(N) O(log N)

19. Common Use Cases

Frequency Counting

for(auto x : arr) {
    mp[x]++;
}
Enter fullscreen mode Exit fullscreen mode

Checking Duplicates

if(mp.count(x)) {
    cout << "Duplicate Found";
}
Enter fullscreen mode Exit fullscreen mode

First Non-Repeating Character

Count frequency
        ↓
Check elements with frequency = 1
Enter fullscreen mode Exit fullscreen mode

Two Sum

Store previously visited numbers
        ↓
Search for required complement
Enter fullscreen mode Exit fullscreen mode

Caching

Input
 ↓
Store Result
 ↓
Same Input?
 ↓
Return Cached Result
Enter fullscreen mode Exit fullscreen mode

Quick Revision Cheat Sheet

HASH MAP
β”‚
β”œβ”€β”€ Stores β†’ Key : Value
β”‚
β”œβ”€β”€ unordered_map
β”‚   β”œβ”€β”€ Average β†’ O(1)
β”‚   └── Unordered
β”‚
β”œβ”€β”€ map
β”‚   β”œβ”€β”€ O(log N)
β”‚   └── Sorted by Key
β”‚
β”œβ”€β”€ Internal Working
β”‚   β”œβ”€β”€ Hash Function
β”‚   β”œβ”€β”€ Hash Code
β”‚   β”œβ”€β”€ Compression Function
β”‚   └── Bucket Array
β”‚
β”œβ”€β”€ Collision
β”‚   β”œβ”€β”€ Separate Chaining
β”‚   └── Open Addressing
β”‚       β”œβ”€β”€ Linear Probing
β”‚       β”œβ”€β”€ Quadratic Probing
β”‚       └── Double Hashing
β”‚
└── Load Factor
    └── N / B
        ↓
      High?
        ↓
     Rehashing
Enter fullscreen mode Exit fullscreen mode

Key takeaway: A hash map trades some extra memory and ordering guarantees for very fast average-case O(1) insertion, lookup, and deletion. It is one of the most important data structures for frequency counting, lookups, caching, and many competitive programming problems.

#include <iostream>
#include <map>
#include <unordered_map>
using namespace std;

int main() {
    // Create an unordered map: Key -> Value
    unordered_map<string, int> mp;


    // -------- INSERTION --------

    // Method 1: make_pair()
    pair<string, int> p = make_pair("rehan", 1);
    mp.insert(p);

    // Method 2: pair constructor
    pair<string, int> p2("shaikh", 2);
    mp.insert(p2);

    // Method 3: [] operator
    mp["bhai"] = 3;


    // -------- UPDATE --------

    // Update value of an existing key
    mp["bhai"] = 4;


    // -------- ACCESSING --------

    // Access using []
    cout << "rehan: " << mp["rehan"] << endl;

    // Access using at()
    cout << "shaikh: " << mp.at("shaikh") << endl;


    // -------- UNKNOWN KEY --------

    // [] creates the key with default value 0
    cout << "Unknown: " << mp["unknownkey"] << endl;

    // at() throws an error if key doesn't exist
    // cout << mp.at("anotherunknown") << endl;


    // -------- SIZE --------

    // Total number of key-value pairs
    cout << "Size: " << mp.size() << endl;


    // -------- CHECK KEY --------

    // count() returns 1 if key exists, otherwise 0
    cout << "Count for rehan: " << mp.count("rehan") << endl;

    // find() returns iterator to the key
    if (mp.find("rehan") != mp.end()) {
        cout << "rehan is present" << endl;
    } else {
        cout << "rehan is not present" << endl;
    }


    // -------- DELETE --------

    // Remove key and its value
    mp.erase("rehan");

    cout << "Size after erase: " << mp.size() << endl;


    // -------- RANGE-BASED LOOP --------

    // Each element is a key-value pair
    // Order is NOT guaranteed in unordered_map
    cout << "\nUsing range-based loop:" << endl;

    for (auto i : mp) {
        cout << i.first << " " << i.second << endl;
    }


    // -------- ITERATOR --------

    // Iterate using an iterator
    cout << "\nUsing iterator:" << endl;

    unordered_map<string, int>::iterator it = mp.begin();

    while (it != mp.end()) {
        cout << it->first << " " << it->second << endl;
        it++;
    }


    // -------- ORDERED MAP --------

    // map stores keys in sorted order
    cout << "\nOrdered map (alphabetical order):" << endl;

    map<string, int> ordered_mp;

    ordered_mp["zebra"] = 10;
    ordered_mp["apple"] = 5;

    map<string, int>::iterator idx = ordered_mp.begin();

    while (idx != ordered_mp.end()) {
        cout << idx->first << " " << idx->second << endl;
        idx++;
    }

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)