Blog

Java HashMap Interview Questions

Key Takeaways

  • HashMap uses hashing to select a bucket and equality to identify a key within that bucket.
  • Expected lookup time is O(1), but O(log n) is not an unconditional worst-case guarantee.
  • Capacity counts buckets; size counts mappings. Sizing affects resizing, memory use, and iteration cost.
  • Keep key equality and hash codes stable. For typical concurrent-map use cases, prefer ConcurrentHashMap over legacy Hashtable.

Java HashMap interview questions test whether you can connect everyday Map operations to their implementation costs. A strong answer explains bucket selection, equality, collisions, resizing, and concurrency—not just that HashMap is “O(1).”

The examples below use Java 21-compatible code. Implementation details are checked against OpenJDK 27; they aren't guarantees of the Map interface.

1) How does put() work internally?

In the OpenJDK implementation, insertion follows this sequence:

  1. Calculate the key's hash code and spread its higher bits into the lower bits. A null key uses hash zero.
  2. Allocate the bucket array if necessary, then select a bucket using the spread hash.
  3. Search that bucket for a matching hash and an identical or equal key. Replace the value if found; otherwise add an entry.
  4. For a new mapping, update the size and check whether growth is needed. A crowded bucket can also trigger collision-handling changes.

The spreading formula is h ^ (h >>> 16); bucket selection is (capacity - 1) & hash. Spreading helps higher hash bits influence bucket selection in a power-of-two table.

Example: capacity = 16

key A: hashCode 3  --> spread hash 3  --> 15 & 3  = 3
key B: hashCode 19 --> spread hash 19 --> 15 & 19 = 3

bucket[3] --> [hash 3, A, value] --> [hash 19, B, value]

Lookup: select bucket --> match hash --> identity / equals check

2) What is HashMap's default initial capacity?

The default initial capacity is 16, and the default load factor is 0.75. However, a default-constructed OpenJDK HashMap has no bucket array yet: its first insertion allocates the table lazily. “Initial capacity 16” doesn't mean construction immediately allocates 16 buckets.

Advertisement

3) How is HashMap implemented in Java?

HashMap is a hash table backed by an array of buckets. Ordinary entries hold a stored hash, key, value, and next-entry reference; crowded buckets can instead use balanced red-black trees. See our Java HashMap implementation explanation for a deeper walkthrough.

HashMap permits one null key and multiple null values. It does not promise insertion order, sorted order, or an iteration order that remains unchanged over time.

4) What does HashMap.put() return?

put(key, value) returns the previous value associated with the key, or null if there was no mapping. Because HashMap permits null values, a null return can also mean the previous value was null. These are the Map return-value semantics, not collision-specific behavior.

Save each complete Java example using its public class name.

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

public class PutDemo {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();

        System.out.println(map.put("name", "Ada"));   // null: new key
        System.out.println(map.put("name", "Grace")); // Ada: replacement

        map.put("nullable", null);
        boolean existed = map.containsKey("nullable");
        String previous = map.put("nullable", "set");
        System.out.println(previous); // null: previous value was null
        System.out.println(existed);  // true
    }
}

Check containsKey() before replacement if you need to distinguish those cases in single-threaded code. Likewise, get() returning null doesn't distinguish an absent key from a null-valued mapping. For more usage patterns, see Java HashMap examples.

5) What is the time complexity of get()?

The interview-ready answer is expected O(1), not guaranteed O(1). Let n be the total number of mappings and m the number in the selected bucket:

  • Well-distributed hashes: expected O(1) lookup.
  • List bucket: O(m) traversal.
  • Tree bucket: typically O(log m) when hashes or usable key comparisons distinguish the search path.
  • Pathological collisions: O(n) remains possible. Equal hashes and non-discriminating comparisons can force a tree lookup to search multiple subtrees.

These costs assume bounded-cost hashing, equality, and key comparisons. Tree balancing alone doesn't make every lookup logarithmic.

6) What happens if initial capacity is too low?

The map can repeatedly allocate larger tables and redistribute entries as it grows. Ordinary growth resizing is O(n) when capacity is proportional to the number of mappings, so an individual insertion can be expensive even though insertion has expected amortized O(1) cost.

Resizing does not normally call every key's hashCode() again: OpenJDK reuses stored hashes. When capacity doubles, an entry either stays at its old bucket index or moves to that index plus the old capacity.

7) What happens if initial capacity is too high?

Once allocated, an oversized table consumes more memory. Iterating HashMap's collection views takes O(capacity + size), so a sparse map can also cost more to traverse than its mapping count suggests. These are concrete costs; don't assert cache-miss penalties without measurement.

8) What is the load factor, and why is the default 0.75?

Load factor connects mapping count to table growth. For ordinary allocated tables, the resize threshold is approximately capacity × load factor, converted to an integer; the normal put path grows the table when adding a mapping makes size exceed that threshold. It isn't a percentage of nonempty buckets.

Default map, ordinary put() growth with well-distributed keys:

construction      first insertion       13th distinct mapping
no table      --> capacity 16       --> capacity 32
size 0            threshold 12          threshold 24

The HashMap documentation describes 0.75 as a time/space trade-off. Higher load factors reduce table-space overhead but increase lookup costs; lower factors favor more space and earlier growth. Keep the default unless measurements justify changing it.

9) How do capacity, size, and expected mappings differ?

  • Size: the number of key/value mappings currently stored.
  • Capacity: the number of buckets in the allocated table.
  • Initial capacity: the requested starting bucket capacity, rounded to a power of two by OpenJDK.
  • Resize threshold: the mapping-count boundary used for normal growth after allocation.

For 1,000 expected mappings at the default load factor, the constructor-capacity calculation is ceil(1000 / 0.75) = 1334. OpenJDK rounds that request to 2,048 buckets on allocation, with a threshold of 1,536.

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

public class SizingDemo {
    public static void main(String[] args) {
        int expectedMappings = 1_000;
        int initialCapacity = (int) Math.ceil(expectedMappings / 0.75d);

        Map<String, Integer> manual = new HashMap<>(initialCapacity);
        Map<String, Integer> preferred = HashMap.newHashMap(expectedMappings);

        System.out.println(initialCapacity); // 1334
        System.out.println(preferred.size()); // 0: sizing doesn't add entries
    }
}

Prefer HashMap.newHashMap(expectedMappings), available since Java 19, when you know the expected mapping count. It uses the default load factor and chooses an initial capacity generally sufficient for that count without resizing. Presize predictable bulk loads; don't reserve enormous tables for speculative growth.

10) What is a collision, and how does HashMap handle it?

Distinguish two cases: unequal keys can have identical hash codes, or different hash codes can select the same bucket. Neither makes the keys equal. HashMap uses equality to decide whether to replace a mapping or retain separate entries.

When does a bucket become a tree?

Java 8 introduced balanced-tree collision handling for crowded HashMap buckets. In OpenJDK 27, TREEIFY_THRESHOLD is 8 and MIN_TREEIFY_CAPACITY is 64; below that table capacity, the implementation resizes instead of treeifying.

11) Why must equals() and hashCode() agree, and why are mutable keys dangerous?

The Object contract requires equal objects to have equal hash codes. Unequal objects may share a hash code. If you override equality without a compatible hash function, equivalent keys can fail to find the same mapping. Our guide to equals() and hashCode() in Java covers that contract in detail.

A record key supplies compatible equality and hash-code implementations based on its components:

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

public class KeyDemo {
    record UserKey(String tenant, long id) {}

    public static void main(String[] args) {
        Map<UserKey, String> owners = new HashMap<>();
        owners.put(new UserKey("acme", 42L), "Ada");

        // Different object, equivalent key.
        System.out.println(owners.get(new UserKey("acme", 42L))); // Ada
    }
}

Records have final component fields, but a component can still reference mutable state. A record containing a mutable collection isn't automatically a safe map key.

If you change a key field used by hashing, lookup uses the key's new hash while the entry retains its insertion-time hash and location. Ordinary lookup and removal can then fail even with the same key object. Changing equality-relevant state also violates the Map contract's assumptions; remove the mapping before such changes, or preferably use immutable key state.

12) Is HashMap thread-safe?

No. Concurrent reads and writes require a synchronization strategy; replacing existing values is still a write even though it isn't a structural modification. Use ConcurrentHashMap for typical shared mutable maps, or protect every participating HashMap access with the same lock.

A fully constructed map can support concurrent reads if you safely publish it and never modify it afterward. Safe publication means establishing visibility between threads—for example, by publishing through a volatile reference under the Java memory model. Mutable keys and values still need their own discipline.

HashMap vs Hashtable vs ConcurrentHashMap

ImplementationConcurrency modelNull keys / valuesTypical fit
HashMapExternal synchronization for shared mutationAllowed / allowedThread-confined or safely published read-only data
HashtableSynchronized individual operationsRejected / rejectedLegacy integration
ConcurrentHashMapThread-safe operations and selected atomic compound APIsRejected / rejectedTypical concurrent mutable maps
Synchronized wrapper over HashMapAccess through a common synchronization wrapperAllowed / allowedWorkflows organized around one lock

Hashtable's own documentation recommends ConcurrentHashMap when you need a highly concurrent implementation. For the historical comparison, see Java HashMap vs Hashtable.

Are multi-step operations atomic?

Not automatically. A get-then-put workflow isn't atomic merely because each call is thread-safe. ConcurrentHashMap provides atomic operations such as putIfAbsent, computeIfAbsent, compute, and merge, but not arbitrary transactions across keys. Keep computation callbacks short because they can block competing updates.

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentCountDemo {
    public static void main(String[] args) {
        var counts = new ConcurrentHashMap<String, Integer>();
        counts.merge("requests", 1, Integer::sum);
        counts.merge("requests", 1, Integer::sum);
        System.out.println(counts.get("requests")); // 2
    }
}

With Collections.synchronizedMap(), hold the wrapper's monitor across an entire compound invariant, and require every participant to follow that locking strategy. Traversing its views also requires synchronization on the wrapper, not on the view. Hashtable's individually synchronized calls don't make arbitrary multi-call workflows atomic either.

Common Mistakes

  • Depending on iteration order: a stable-looking HashMap order isn't a contract.
  • Treating fail-fast iteration as synchronization: ConcurrentModificationException is a best-effort bug detector, not a concurrency guarantee.
  • Confusing a concurrent map with concurrent values: protecting map operations doesn't automatically protect mutable objects stored inside it.

Use the documented iteration and concurrency guarantees rather than behavior observed in a small test.

FAQ

What is the default capacity and load factor of a Java HashMap?

The default initial capacity is 16 and the default load factor is 0.75. OpenJDK allocates a default-constructed map's bucket array lazily on its first insertion, rather than during construction.

What does HashMap.put() return?

HashMap.put() returns the previous value associated with the key, or null if no mapping existed. Because null values are allowed, null can also mean the previous mapping contained a null value.

Can HashMap have a null key or null values?

Yes. HashMap permits one null key and multiple null values; ConcurrentHashMap and Hashtable reject both null keys and null values.

Why should HashMap keys be immutable?

Changing state used by equals() or hashCode() while a key is stored can make its mapping unreachable through ordinary lookup or removal. The key object need not be completely immutable, but its equality- and hash-relevant state must remain stable.

Is HashMap thread-safe?

No. Use external synchronization for concurrent mutation or choose ConcurrentHashMap for typical shared mutable maps. Thread-safe individual calls still don't make an arbitrary sequence of calls atomic.

Further Reading

Advertisement

Written by Sam Erickson

Pen name of a software engineer with 20+ years in the industry, from Fortune 500 companies to startups. Newer articles are researched and drafted with AI tools I direct and fact-checked against their sources. Author of Mastering the Tech Interview. More about me

New · $49AI Engineer Interview KitSee what's inside →
Advertisement
Java HashMap Interview Questions: Internals & Answers | StackChief