Data Structures & Algorithms (DSA) Module 3 – Hashing: maps, sets and hash functions
Hash maps in five standard libraries
Use Python dict, Java HashMap, C++ unordered_map, Go maps and JavaScript Map well: missing keys, iteration order, key rules and the trap of each library.
What you will learn
- Use dict and set, HashMap and HashSet, unordered_map, Go maps and JavaScript Map and Set idiomatically
- Pick keys that hash and compare correctly
- Avoid the common correctness and performance traps
- Group and count items with a map, then report them in sorted order
Before you start
What you’ll learn · Before you start
What you will learn
- Use dict and set, HashMap and HashSet, unordered_map, Go maps and JavaScript Map and Set idiomatically
- Pick keys that hash and compare correctly
- Avoid the common correctness and performance traps
- Group and count items with a map, then report them in sorted order
Before you start
On this page
Every mainstream language ships a hash map, and the five covered here, Python’s dict and set, Java’s HashMap and
HashSet, C++’s unordered_map and unordered_set, Go’s built-in map and JavaScript’s Map and Set, all give
insert, find and delete in O(1) time on average. They differ in four places, and those four cause most of the bugs
people write with them: what reading a missing key does, the order in which a loop visits the keys, what may be a
key, and how two keys are judged equal. This lesson runs one counting task in two languages, puts the five libraries
side by side on those four questions, and names the trap each library is known for.
The same count in two languages
Counting how many roll numbers belong to each branch, given by the first three characters, takes one map from branch code to count and one pass:
Python · count/branches.py
"""Count roll numbers by branch code (the first three characters) and print the branches in sorted order."""
rolls = ["ECE21004", "CSE21001", "MEC21010", "CSE21007", "ECE21002", "CSE21013", "CIV21003"]
counts = {}
for roll in rolls:
branch = roll[:3]
counts[branch] = counts.get(branch, 0) + 1 # get() returns 0 for a branch not seen yet
print("as inserted:", counts)
for branch in sorted(counts):
print(f"{branch}: {counts[branch]}") Output
as inserted: {'ECE': 2, 'CSE': 3, 'MEC': 1, 'CIV': 1}
CIV: 1
CSE: 3
ECE: 2
MEC: 1
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 branches.py
JavaScript · count/branches.mjs
// Count roll numbers by branch code (the first three characters) and print the branches in sorted order.
const rolls = ['ECE21004', 'CSE21001', 'MEC21010', 'CSE21007', 'ECE21002', 'CSE21013', 'CIV21003'];
const counts = new Map();
for (const roll of rolls) {
const branch = roll.slice(0, 3);
counts.set(branch, (counts.get(branch) ?? 0) + 1); // get() returns undefined for a branch not seen yet
}
console.log('as inserted:', [...counts.keys()].join(' '));
for (const branch of [...counts.keys()].sort()) console.log(`${branch}: ${counts.get(branch)}`); Output
as inserted: ECE CSE MEC CIV CIV: 1 CSE: 3 ECE: 2 MEC: 1
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node branches.mjs
Runs on this device, in your browser. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Both programs read a missing branch as zero in one expression, counts.get(branch, 0) in Python and
counts.get(branch) ?? 0 in JavaScript, because a plain read of a missing key gives no number in either language.
Both maps list the branches in the order they first appeared, ECE before CSE, so both sort the keys before printing.
Five libraries side by side
| Criterion | Python dict / set | Java HashMap / HashSet | C++ unordered_map / set | Go map | JavaScript Map / Set |
|---|---|---|---|---|---|
| Reading a missing key | d[k] raises KeyError; d.get(k) gives None | get(k) returns null | m[k] inserts a default value; find(k) returns end() | m[k] returns the zero value | get(k) returns undefined |
| Is the key there? | k in d | containsKey(k) | contains(k), C++20; or find | v, ok := m[k] | has(k) |
| Iteration order | dict: insertion order; set: none | None promised | Unspecified | Unspecified, different on each loop | Insertion order |
| What a key needs | __hash__ and __eq__; built-in mutable types refused | equals and hashCode that agree | A std::hash specialisation and == | A comparable type: no slices, maps or functions | Anything; compared with SameValueZero |
| When to choose | Counter and defaultdict for counting and grouping | merge and getOrDefault for counting | reserve before bulk inserts; find for reads | comma-ok for presence; sort keys for output | Map, not a plain object, for data keys |
Read the first row before writing a lookup in an unfamiliar language: the same expression, m[k], raises an error in
Python, quietly inserts in C++, returns 0 in Go and is a different operation altogether on a JavaScript Map. The
third row is why the counting programs above sort before they print.
Sets in the five libraries
A set is the same table without values, and each library has one. Python has set and the unchangeable frozenset,
which can itself be a key. Java’s HashSet is documented as backed by a HashMap, so it inherits that map’s costs
and its lack of order. C++ has unordered_set. JavaScript has Set, with the same insertion order and key
comparison as Map. Go has no set type; the usual stand-in is a map[string]struct{}, whose empty struct values take
no space at all, or a map[string]bool when reading a missing key as false is convenient.
The trap each library is known for
- Python: a
defaultdictcreates an entry for every missing key it is asked about, even in a loop that only reads. - Java: comparing two
Integervalues with==compares objects, so it works for small numbers and then fails. - C++: reading with
operator[]inserts the key when it is missing, so a loop that only reads can grow the map. - Go: a loop over a map visits the keys in a different order each time, so output needs sorted keys.
- JavaScript: a plain object turns every key into a string, so
1and"1"become the same key.
Each of these compiles, runs and passes a quick test, which is what makes it dangerous: the map still works, but reports a size that is too large, a comparison that is wrong only for larger numbers, or an order that changes between runs.
Choosing keys that hash and compare correctly
A key must keep its hash for as long as it is stored, so use values that cannot change: strings, numbers and tuples
in Python, records or strings in Java, structs of comparable fields in Go. Build composite keys from their parts
rather than by gluing strings together, because “AB” + “C” and “A” + “BC” give the same text: a tuple (city, day)
in Python, a record in Java, a struct in Go. In C++, std::pair has no standard std::hash, so a pair key needs a
hash function of your own. In JavaScript, a Map compares object keys by identity, so two objects with the same
contents are two different keys; turn such a key into a string or a number first.
Check yourself
Check yourself
4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Each library's trap, reproduced and fixed Pro: opens with a Pro, Premium or Ultimate pass
Every trap in the list above fits in a few lines, and each has a one-line fix. This part runs all five, in Python, Java, C++, Go and JavaScript, with the output recorded, and shows the fix next to the failure.
In this part:
- Python: get, Counter and the keys a dict refuses
- Java: == on Integer values, and equals without hashCode
- C++: operator[] inserts; find and contains do not
- Go: comma-ok, the zero value and sorted output
- JavaScript: Map against a plain object
- The five fixes in one place
6 runnable examples (C++, Go, Java, JavaScript, Python) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Memory, presizing and ordered alternatives, measured Pro: opens with a Pro, Premium or Ultimate pass
A hash map’s convenience has a price in memory and in resizing, and when the order of keys matters it is the wrong structure. This part measures both costs and lists the ordered alternatives in each language.
In this part:
- How a dict grows, and what an entry costs
- Presizing: reserve, newHashMap and the rehash count
- When order matters: the ordered alternatives
- Practise: group roll numbers by branch
- Group roll numbers by branch, in sorted order
- Results of the sample tests
3 runnable examples (C++, Python) · 1 exercise with sample tests · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher to mid
A Java leaderboard keeps scores in a HashMap<String, Integer> and declares a tie when map.get(a) == map.get(b). Every test with scores below 100 passes, but in production two players with 450 points each are not tied. Explain why, and fix it.
-
Mid to senior
A C++ service keeps its settings in an unordered_map<string, string> and only reads it after start-up, yet its memory grows by tens of megabytes a day and the map's size() keeps rising. What is the likely cause, how do you prove it, and how do you prevent it for good?
-
Fresher to mid
You need a map keyed by a city and a day number in Python, Java and Go, and a colleague suggests the string city + day as the key. How would you build the key in each language so that hashing and equality are right, and what can go wrong with the string?
Key takeaways
- All five libraries give O(1) average insert, find and delete, but they differ on missing keys, iteration order, key requirements and equality.
- A missing key raises in Python, returns
nullin Java, inserts in C++ with[], returns the zero value in Go andundefinedfrom a JavaScriptMap. - Only Python’s
dictand JavaScript’sMappromise insertion order; sort the keys whenever output order matters. - Keys must not change while stored; build composite keys from their parts, not by gluing strings.
References
- Built-in types: mapping types (dict) (Python Software Foundation)
- collections: Counter and defaultdict (Python Software Foundation)
- sys.getsizeof (Python Software Foundation)
- tracemalloc: trace memory allocations (Python Software Foundation)
- Java SE 25 API: HashMap (Oracle)
- Java SE 25 API: Integer.valueOf(int) (Oracle)
- Java SE 25 API: Object.hashCode() (Oracle)
- Java SE 25 API: Record (Oracle)
- Java SE 25 API: TreeMap (Oracle)
- std::unordered_map (cppreference.com)
- std::unordered_map::operator[] (cppreference.com)
- std::unordered_map::reserve (cppreference.com)
- The Go Programming Language Specification: map types and index expressions (The Go Authors)
- Package maps: Keys (The Go Authors)
- Map (JavaScript reference): key equality (MDN Web Docs)
- Property accessors (JavaScript reference): property names (MDN Web Docs)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress