HashMap and how it works internally

HashMap finds a value by its key in about the same time whether it holds ten entries or ten million. That speed comes from a simple idea: turn the key into a number, and use the number to jump straight to a small bucket. You'll learn to use HashMap fluently, then open it up: how the bucket is chosen, what happens when two keys land in the same bucket, why the table doubles when it's 75% full, and the bugs that appear when keys misbehave. This is one of the most asked topics in Java interviews.

Module 11 · Collections⏱ 28 minIntermediate8 practice problems
1 The story

Sixteen pigeonholes in the staff room

Arjun Sir's staff room has a wall of 16 pigeonholes for letters. A clerk doesn't search all 16: she takes the teacher's name, does a quick calculation that always gives the same answer for the same name, and drops the letter straight into that hole. When two teachers' names give the same hole, both letters sit there, and she checks the names when someone collects.

"idli" "samosa" "chai" hash → 9hash → 9hash → 3 table[16] [3]chai=15 [9]idli=30→samosa=20a chain other 14 buckets empty for now myphatshala.in
Riya

Sir, that's exactly what the canteen app needs. Prices by dish name, instantly.

Arjun Sir

And it's exactly what a HashMap does. The “quick calculation” is the key's hashCode(). The pigeonholes are an array of buckets. When two keys share a bucket, the entries are chained and equals() picks the right one.

Riya

What if the canteen grows to 500 dishes? Sixteen holes would be stuffed.

Arjun Sir

Then the map does what the clerk would: when it's 75% full, it builds a wall with twice as many holes and re-sorts the letters. Let's watch every step happen in a real JVM.

2 Definition

What is a HashMap?

Formal definition (write this in exams)

java.util.HashMap<K,V> is a hash-table based implementation of the Map interface that stores key–value pairs with unique keys. It permits one null key and any number of null values, makes no guarantee about iteration order, and is not synchronised. Internally it keeps an array of buckets (table); a key's bucket index is computed from its hashCode() (spread by hash()) as (n − 1) & hash. Entries in the same bucket form a linked list, converted to a balanced tree when a bucket grows large. When the number of entries exceeds capacity × load factor (default 16 × 0.75 = 12), the table doubles and entries are redistributed. Average time for get and put is O(1).

In simple words

A dictionary: give it a key, get the value. Speed comes from computing where the key lives instead of searching for it.

Words you'll meet in this lesson

Entry / NodeOne key–value pair, stored as Node{hash, key, value, next}.
BucketOne slot of the table array; holds zero, one or a chain of nodes.
CollisionTwo different keys map to the same bucket.
CapacityNumber of buckets: 16 by default, always a power of two.
Load factorHow full the table may get before growing: 0.75 by default.
Thresholdcapacity × load factor. Exceed it and the table doubles (rehash).
TreeifyTurning a long chain (8+ nodes) into a red-black tree, once the table has 64+ buckets.
Default capacity / load factor16 buckets, 0.75 → first resize when the 13th entry is added
Bucket index(n − 1) & hash, where hash = h ^ (h >>> 16) and h = key.hashCode()
CollisionsChained in a linked list (new nodes at the tail); a tree when a chain reaches 8 and capacity ≥ 64
NullOne null key (always bucket 0), many null values. Hashtable allows neither
Costget/put/remove O(1) average; worst case O(log n) with treeified buckets

Exam tip: the steps of put(k, v): (1) hash(k), (2) index = (n − 1) & hash, (3) empty bucket → new node; otherwise walk the chain comparing hash and equals: same key → replace the value (and return the old one), else append a node, (4) if ++size > threshold → resize to double capacity. Learn this sequence word for word.

3 Types and ways to use it

Using a HashMap well

Put and update

put, putIfAbsent, merge, compute.

write

Look up

get, getOrDefault, containsKey, containsValue.

read

Iterate

entrySet(), keySet(), values(), forEach.

walk

Ordered cousins

LinkedHashMap (insertion order), TreeMap (sorted keys).

order

Everyday operations

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

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> price = new HashMap<>();
        System.out.println(price.put("chai", 15));      // null: no old value
        System.out.println(price.put("chai", 20));      // 15: old value replaced
        price.put("dosa", 60);
        price.putIfAbsent("dosa", 99);                   // ignored: key exists

        System.out.println(price.get("chai") + " " + price.get("tea"));
        System.out.println(price.getOrDefault("tea", 0) + " " + price.containsKey("dosa"));

        Map<String, Integer> sold = new HashMap<>();
        for (String d : new String[]{"chai", "dosa", "chai"}) {
            sold.merge(d, 1, Integer::sum);              // count: start at 1 or add 1
        }
        for (Map.Entry<String, Integer> e : sold.entrySet()) {
            System.out.println(e.getKey() + " sold " + e.getValue());
        }
    }
}
null 15 20 null 0 true chai sold 2 dosa sold 1

merge and Integer::sum use a method reference (Lesson 14.1); read it as “if the key is new, store 1, otherwise add 1 to the old value”.

Collisions are normal

public class Main {
    public static void main(String[] args) {
        // different keys, same hash code: a true collision
        System.out.println("Aa".hashCode() + " " + "BB".hashCode());
        // different hash codes, same bucket in a 16-bucket table
        System.out.println(index("idli", 16) + " " + index("samosa", 16));
        System.out.println(index("idli", 32) + " " + index("samosa", 32));   // after doubling
    }

    static int index(String key, int buckets) {
        int h = key.hashCode();
        int hash = h ^ (h >>> 16);           // HashMap.hash(): mix high bits into low bits
        return (buckets - 1) & hash;          // same as hash % buckets for powers of two
    }
}
2112 2112 9 9 9 25

With 16 buckets, “idli” and “samosa” share bucket 9. When the table grows to 32, one more bit of the hash is used and they separate: idli stays at 9, samosa moves to 9 + 16 = 25.

The mutable-key bug

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> seat = new ArrayList<>(List.of(4, 32));   // coach 4, seat 32
        Map<List<Integer>, String> booked = new HashMap<>();
        booked.put(seat, "Riya");

        seat.set(1, 33);                     // key changed after being stored!
        System.out.println(booked.get(seat) + " " + booked.get(List.of(4, 32)) + " " + booked.size());
    }
}
null null 1

Keys must not change while in a map. The entry sits in the bucket chosen by the old hash. After the change, the new hash points to another bucket, and the old key no longer equals anything you can build. The entry is stuck: size 1, but unreachable. Use immutable keys: String, Integer, enums, records.

Rule of thumb: HashMap for lookups; LinkedHashMap when output should follow insertion order; TreeMap when keys must be sorted or you need floorKey/ceilingKey; ConcurrentHashMap when several threads write. If you know the size, pre-size it: new HashMap<>(expected * 4 / 3 + 1), or HashMap.newHashMap(expected) on Java 19+.

4 Step by step

Inside put("samosa", 20)

  1. Compute the hash

    int h = key.hashCode();          // "samosa" → -909675842
    int hash = h ^ (h >>> 16);       // spread the high bits down
    Only the low bits pick the bucket, so hash() mixes the top 16 bits into the bottom 16. That way keys that differ only in high bits don't all collide.
  2. Find the bucket

    index = (n − 1) & hash. With n = 16, n − 1 is binary 1111, so this keeps the last 4 bits: a number from 0 to 15. Faster than %, and the reason capacity is always a power of two.
  3. Bucket occupied? Walk the chain

    Bucket 9 already holds “idli”. For each node, the map compares the stored hash first (cheap) and then equals. “idli” ≠ “samosa”, so it keeps walking. If an equal key were found, its value would be replaced and the old value returned.
  4. Append a new node

    A Node{hash, "samosa", 20, next=null} is linked after “idli”. If this chain reached 8 nodes, the bucket would be converted to a red-black tree (or the table resized, if it has fewer than 64 buckets).
  5. Check the threshold

    ++size > threshold? With capacity 16 the threshold is 12, so the 13th entry triggers resize(): a new table of 32, and every node moves either to the same index or to index + 16, depending on one extra hash bit. No full rehash of keys is needed.
5 See it happen

put, collide, get, resize: both views

Left: map calls in your code. Right: the bucket table, a chain forming in bucket 9, and what happens when the table doubles.

Inside a HashMap 6 steps
What you see price.put("chai", 15) price.put("idli", 30) price.put("samosa", 20) price.put("chai", 20) price.get("samosa") // … 13th entry added returns null null null 15 (old value) 20 map still works, just faster Add chai. Add idli. Add samosa. Update chai's price. Look up samosa. The map grows. myphatshala.in
Behind the screen table (16 buckets) table (32 buckets) after resize [3] [9] chai = 15 chai = 20 idli = 30 samosa = 20 [3] chai = 20 [9] idli = 30 [25] samosa = 20 moved 9 → 9 + 16 hash("chai") → index 3: empty → new node hash("idli") → index 9: empty → new node hash("samosa") → index 9: idli ≠ samosa → chain index 3: equals("chai") → replace value index 9: idli? no → samosa? yes → 20 13 > 16 × 0.75 → double: 9 stays, samosa → 25 One computed jump, no search. Different key, different bucket. A collision: both live in bucket 9. Same key: the node is updated in place. Hash finds the bucket; equals finds the key. Resize splits the chain using one more hash bit. myphatshala.in
6 Real-world use

HashMap in real systems

Hash maps are the workhorse of backend code: caches, indexes, counters and lookups.

Watch it with an example: an IFSC lookup at a bank

A payments service maps IFSC codes to branch details. Watch one lookup: no scanning of thousands of branches, just a computed jump and one or two equals checks.

Looking up an IFSC code 4 steps
Animated example "SBIN0001234" hash bucket 1,847 of 4,096 HDFC0000240 ✗ SBIN0001234 ✓ SBI · Kharghar · Navi Mumbai A key arrives. Its hash picks one bucket out of thousands. A short chain: equals finds the match. The value comes back. myphatshala.in
session id → user Riya

1. Sessions and caches

Web servers map session IDs to logged-in users; services cache exchange rates, product details and config by key to avoid slow database calls.

request cookie→sessions.get(id)→user, in O(1)
"UPI" → 1,204"CARD" → 388

2. Counting and grouping

Orders per city, errors per type, votes per candidate: merge(key, 1, Integer::sum) or computeIfAbsent(key, k -> new ArrayList<>()).add(item) for grouping.

event→counts.merge(type, 1, Integer::sum)→dashboard

3. Joining data in memory

To combine orders with customers, load customers into a map by ID once, then look each order's customer up in O(1), instead of a nested loop.

10k customers → map→byId.get(order.custId)→enriched orders
1 bucket,10,000 keys

4. Hash flooding and bad hash codes

Attackers once sent web servers thousands of parameters with colliding hash codes to slow them down. Java 8's treeified buckets cap the damage at O(log n). A home-made hashCode() that returns a constant causes the same slowdown by accident.

bad hashCode→every key in one bucket→O(1) becomes slow

Examples are simplified. Shared caches across servers use stores like Redis; inside one JVM with many threads, ConcurrentHashMap.

7 Run the code

Canteen billing with a price map and a sales counter

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> price = new HashMap<>();
        price.put("chai", 15);
        price.put("idli", 30);
        price.put("samosa", 20);
        price.put("dosa", 60);

        String[] orders = {"chai", "idli", "chai", "pav", "samosa", "chai"};
        Map<String, Integer> sold = new TreeMap<>();     // sorted report
        int bill = 0;
        List<String> unknown = new ArrayList<>();

        for (String item : orders) {
            Integer p = price.get(item);
            if (p == null) {
                unknown.add(item);
                continue;
            }
            bill += p;
            sold.merge(item, 1, Integer::sum);
        }

        System.out.println("Sold: " + sold);
        System.out.println("Bill: ₹" + bill + ", not on menu: " + unknown);
        for (Map.Entry<String, Integer> e : sold.entrySet()) {
            System.out.println(e.getKey() + " → ₹" + e.getValue() * price.get(e.getKey()));
        }
    }
}
Sold: {chai=3, idli=1, samosa=1} Bill: ₹95, not on menu: [pav] chai → ₹45 idli → ₹30 samosa → ₹20
  • price.get(item)O(1) lookup; returns null for an unknown dish, so the result is kept as Integer.
  • sold.merge(item, 1, Integer::sum)First time: puts 1. Later: adds 1.
  • new TreeMap<>()The report prints dishes alphabetically.
  • entrySet()The efficient way to walk keys and values together.
8 Line by line

A real collision in bucket 9

This trace is recorded from the real JVM. The heap line shows the map's bucket table: [9] "idli"=30 → "samosa"=20 means two entries chained in bucket 9. Watch the update of an existing key and a lookup that walks the chain.

Worked examples: real problems, solved and traced in memory

Three classic HashMap tasks, traced on the real JVM. The first one shows a resize happening.

Example 1Internals · resize

Watch the table double at the 13th entry

Problem statement

Add 13 menu items to a HashMap and observe, on the heap, the table growing from 16 to 32 buckets and the idli/samosa chain splitting.

Input
13 dishes
Expected output
13 dishes

Approach

  1. Put the dishes in a loop.
  2. Watch the heap line: “16 buckets” until size 12, then “32 buckets”.
  3. Notice samosa moving from bucket 9 to bucket 25.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Logistics · grouping

Group parcels by city

Problem statement

Parcels arrive as "id:city". Group parcel IDs by city with computeIfAbsent and print the groups in city order.

Input
["P1:Pune", "P2:Mumbai", "P3:Pune", "P4:Thane"]
Expected output
{Mumbai=[P2], Pune=[P1, P3], Thane=[P4]}

Approach

  1. A TreeMap<String, List<String>> so cities print sorted.
  2. For each parcel, computeIfAbsent(city, k -> new ArrayList<>()) returns the city's list, creating it the first time.
  3. Add the ID to that list.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Retail · two-sum

Two items that use a gift card exactly

Problem statement

A customer has a ₹500 gift card. From prices [120, 260, 380, 240], find two different items whose prices add up to exactly 500, in one pass.

Input
prices [120, 260, 380, 240], target 500
Expected output
Items 0 and 2: ₹120 + ₹380

Approach

  1. Keep a map from price → index of items seen so far.
  2. For each price p, check whether 500 − p is already in the map.
  3. If yes, report the pair; otherwise store p. O(n) instead of checking every pair.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 11.6 in one screen

  • HashMap: key → value, unique keys, one null key, unordered, O(1) average.
  • Inside: Node[] table; node = hash, key, value, next.
  • put: hash = h ^ (h >>> 16) → index (n−1) & hash → empty? insert : walk chain (hash + equals) → replace or append → size > threshold? resize.
  • Defaults 16 / 0.75 → resize at the 13th entry → 32 buckets; nodes go to index or index + oldCap.
  • Chains → tree at 8 nodes (if capacity ≥ 64); back to list at 6.
  • Needs correct, stable equals/hashCode; never mutate keys.
  • Use: getOrDefault, putIfAbsent, merge, computeIfAbsent, entrySet. Ordered: LinkedHashMap, TreeMap. Threads: ConcurrentHashMap.

Interview and viva questions

Explain how HashMap works internally.

It's an array of buckets. put computes the key's hash (hashCode with high bits spread), finds the bucket with (n − 1) & hash, and either inserts a new node, or walks the bucket's chain comparing hash and equals to replace an existing key's value or append a node. When size exceeds capacity × 0.75, the table doubles and nodes are split between index and index + old capacity. Long chains (8+) become red-black trees when the table has at least 64 buckets. get follows the same hash → bucket → equals path.

What happens if hashCode is good but equals is not overridden?

Equal-looking keys may land in the same bucket, but equals (identity) says they differ, so you get duplicates and failed lookups with a new key object. Both methods must be overridden consistently (Lesson 10.3).

Why is the capacity always a power of two?

So the index can be computed with a fast bit mask, (n − 1) & hash, instead of the slower %, and so that on resize each bucket splits cleanly into two (same index or index + old capacity) by checking a single bit.

HashMap vs Hashtable vs ConcurrentHashMap?

HashMap: not thread-safe, allows one null key and null values. Hashtable: legacy, every method synchronised, no nulls. ConcurrentHashMap: thread-safe with fine-grained locking/CAS, high concurrency, no null keys or values. Use ConcurrentHashMap for shared maps.

Why did Java 8 add treeification?

If many keys collide (poor hashCode or a deliberate attack), a chain could grow to thousands of nodes and make get/put O(n). Converting large buckets to red-black trees keeps the worst case at O(log n).

Practice problems

Use HashMap fluently and reason about its internals. The last three are business tasks.

P1Easy

Character frequency

Count how many times each character appears in "banana", printed in alphabetical order.

Input
"banana"
Expected output
{a=3, b=1, n=2}
Show solution and explanation
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<Character, Integer> freq = new TreeMap<>();
        for (char c : "banana".toCharArray()) freq.merge(c, 1, Integer::sum);
        System.out.println(freq);
    }
}
{a=3, b=1, n=2}

How it works: merge inserts 1 for a new key or adds 1 to the existing count.

P2Easy

Iterate entries

Print each state with its capital from a map of three states, one per line, in insertion order.

Input
Maharashtra, Karnataka, Gujarat
Expected output
Maharashtra → Mumbai
Karnataka → Bengaluru
Gujarat → Gandhinagar
Show solution and explanation
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, String> cap = new LinkedHashMap<>();
        cap.put("Maharashtra", "Mumbai");
        cap.put("Karnataka", "Bengaluru");
        cap.put("Gujarat", "Gandhinagar");
        for (Map.Entry<String, String> e : cap.entrySet()) {
            System.out.println(e.getKey() + " → " + e.getValue());
        }
    }
}
Maharashtra → Mumbai Karnataka → Bengaluru Gujarat → Gandhinagar

How it works: entrySet() gives key and value together; LinkedHashMap keeps insertion order.

P3Easy

Compute the bucket index

Write the HashMap index formula and print the 16-bucket index of "Aa", "BB" and "chai".

Input
"Aa", "BB", "chai"
Expected output
Aa→0 BB→0 chai→3 
Show solution and explanation
public class Main {
    static int index(Object key, int n) {
        int h = key.hashCode();
        return (n - 1) & (h ^ (h >>> 16));
    }

    public static void main(String[] args) {
        for (String k : new String[]{"Aa", "BB", "chai"}) System.out.print(k + "→" + index(k, 16) + " ");
        System.out.println();
    }
}
Aa→0 BB→0 chai→3

How it works: "Aa" and "BB" have the same hash code (2112), so they always collide.

P4Medium

First non-repeating character

Find the first character in "swiss" that appears only once.

Input
"swiss"
Expected output
w
Show solution and explanation
import java.util.*;

public class Main {
    public static void main(String[] args) {
        String s = "swiss";
        Map<Character, Integer> count = new LinkedHashMap<>();
        for (char c : s.toCharArray()) count.merge(c, 1, Integer::sum);
        for (Map.Entry<Character, Integer> e : count.entrySet()) {
            if (e.getValue() == 1) { System.out.println(e.getKey()); break; }
        }
    }
}
w

How it works: a LinkedHashMap counts in one pass and remembers first-seen order for the second pass. O(n).

P5Medium

Invert a map

Invert {chai=15, vada=25, poha=15} into price → list of dishes, sorted by price.

Input
{chai=15, vada=25, poha=15}
Expected output
{15=[chai, poha], 25=[vada]}
Show solution and explanation
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> price = new LinkedHashMap<>();
        price.put("chai", 15); price.put("vada", 25); price.put("poha", 15);
        Map<Integer, List<String>> byPrice = new TreeMap<>();
        for (Map.Entry<String, Integer> e : price.entrySet()) {
            byPrice.computeIfAbsent(e.getValue(), k -> new ArrayList<>()).add(e.getKey());
        }
        System.out.println(byPrice);
    }
}
{15=[chai, poha], 25=[vada]}

How it works: values may repeat, so the inverted map's values are lists.

P6Business problem

Stock after orders

Stock: pen 10, book 4. Orders: pen 3, book 5, pen 2. Fulfil an order only if enough stock exists; print rejected orders and final stock.

Input
3 orders
Expected output
Rejected: book x5
{book=4, pen=5}
Show solution and explanation
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> stock = new TreeMap<>(Map.of("pen", 10, "book", 4));
        String[] item = {"pen", "book", "pen"};
        int[] qty = {3, 5, 2};
        for (int i = 0; i < item.length; i++) {
            int have = stock.getOrDefault(item[i], 0);
            if (have >= qty[i]) stock.put(item[i], have - qty[i]);
            else System.out.println("Rejected: " + item[i] + " x" + qty[i]);
        }
        System.out.println(stock);
    }
}
Rejected: book x5 {book=4, pen=5}

How it works: each order is one O(1) lookup and one update.

P7Business problem

Join orders with customers

Customers: (1, Riya), (2, Kabir). Orders: (101, cust 2), (102, cust 1), (103, cust 2). Print each order with the customer name, using a map instead of a nested loop.

Input
2 customers, 3 orders
Expected output
Order 101 → Kabir
Order 102 → Riya
Order 103 → Kabir
Show solution and explanation
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> customer = new HashMap<>();
        customer.put(1, "Riya");
        customer.put(2, "Kabir");
        int[][] orders = {{101, 2}, {102, 1}, {103, 2}};
        for (int[] o : orders) {
            System.out.println("Order " + o[0] + " → " + customer.get(o[1]));
        }
    }
}
Order 101 → Kabir Order 102 → Riya Order 103 → Kabir

How it works: building the map is O(c) and each lookup O(1), so the join is O(c + o) instead of O(c × o).

P8Business problem

Rate-limit API keys

Each API key may make at most 3 calls. Process calls K1, K2, K1, K1, K1, K2 and print which are allowed.

Input
K1, K2, K1, K1, K1, K2
Expected output
K1 allowed (1)
K2 allowed (1)
K1 allowed (2)
K1 allowed (3)
K1 BLOCKED
K2 allowed (2)
Show solution and explanation
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> used = new HashMap<>();
        for (String key : new String[]{"K1", "K2", "K1", "K1", "K1", "K2"}) {
            int n = used.merge(key, 1, Integer::sum);
            System.out.println(key + (n <= 3 ? " allowed (" + n + ")" : " BLOCKED"));
        }
    }
}
K1 allowed (1) K2 allowed (1) K1 allowed (2) K1 allowed (3) K1 BLOCKED K2 allowed (2)

How it works: merge returns the new count, so the check and update are one call. (A real limiter also resets counts per time window and uses ConcurrentHashMap.)

Real-world scenario questions (asked in company interviews)

HashMap is the single most asked Java collections topic. Interviewers love scenarios where the internals explain a production bug.

Backend · performanceJunior

A cache that got slower as it grew

A product cache uses HashMap<ProductKey, Product>. Lookups were fast with 1,000 entries but take milliseconds with 100,000. Profiling shows time spent in ProductKey.equals. What's your hypothesis?

The interviewer is checking: the effect of a poor hashCode.

Show a strong answer
  • Most keys probably share a few hash codes (e.g. hashCode uses only a category field, or returns a constant), so buckets hold long chains and each lookup calls equals many times.
  • Check bucket distribution in a test; fix hashCode to use all fields that equals uses (Objects.hash(...)) or make the key a record.
  • Treeification softens it to O(log n) only if keys are Comparable; a good hash is the real fix.

Avoid: increasing the initial capacity. More buckets don't help if every key hashes to the same value.

Railways · bugJunior

A booking that can't be found or removed

Bookings are stored in a HashMap<Seat, Booking>. After a passenger changes coach (the code updates seat.coach), map.get(seat) returns null and map.remove(seat) does nothing, but size() still counts it. Explain.

The interviewer is checking: mutable keys and bucket placement.

Show a strong answer
  • The entry was placed by the old hash. Changing a field used in hashCode changes the hash, so lookups go to a different bucket.
  • Make Seat immutable (final fields, or a record). To change seat: remove(oldSeat), then put(newSeat, booking).
  • Add a test that mutating keys is impossible (no setters).

Avoid: “just iterate the map to find it”. That turns O(1) into O(n) and hides the design flaw.

Platform · concurrencyMid

Lost counts under load

A shared HashMap<String, Integer> counts API calls per client from many request threads with map.put(k, map.getOrDefault(k, 0) + 1). Under load the totals are too low, and once a thread got stuck at 100% CPU. What's happening?

The interviewer is checking: HashMap isn't thread-safe.

Show a strong answer
  • Read-modify-write races lose increments; concurrent resizes can corrupt the table (older JDKs could even form a cycle in a chain, causing infinite loops).
  • Use ConcurrentHashMap with merge(k, 1, Integer::sum), or ConcurrentHashMap<String, LongAdder> for very hot counters.
  • Module 12 covers why races happen.

Avoid: Collections.synchronizedMap with the same get-then-put code; the compound action is still not atomic.

Batch · memoryJunior

Loading 10 lakh customers into a map

A job builds a HashMap of 1,000,000 customers by ID. It's slower than expected and memory spikes during loading. What would you change?

The interviewer is checking: resizing cost and pre-sizing.

Show a strong answer
  • Starting at 16 buckets, the map resizes about 17 times, each time allocating a doubled table and moving every node.
  • Pre-size: capacity ≥ expected / 0.75 (e.g. HashMap.newHashMap(1_000_000) on Java 19+, or new HashMap<>(1_333_334)).
  • Consider primitive-keyed maps from a library if memory is critical, or stream the data instead of holding it all.

Avoid: new HashMap<>(1_000_000) thinking it holds a million without resizing; with load factor 0.75 it still resizes once.