[{"data":1,"prerenderedAt":143},["ShallowReactive",2],{"qa-\u002Fjava\u002Fcollections\u002Fhashmap-internals":3},{"page":4,"siblings":122,"blog":140},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":112,"related":113,"seo":114,"seoDescription":115,"stem":116,"subtopic":117,"topic":118,"topicSlug":119,"updated":120,"__hash__":121},"qa\u002Fjava\u002Fcollections\u002Fhashmap-internals.md","Hashmap Internals",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"hard","md","Java","java",{},true,"\u002Fjava\u002Fcollections\u002Fhashmap-internals",[22,27,31,35,39,43,47,51,55,59,63,67,71,75,79,83,87,91,95,99,103,108],{"id":23,"difficulty":24,"q":25,"a":26},"how-hashmap-works","medium","How does a HashMap work internally?","A `HashMap` stores entries in an **array of buckets** (`Node\u003CK,V>[] table`).\nEach bucket holds a chain of `Node` objects, where a `Node` packs the key's\n**hash**, the **key**, the **value**, and a **`next`** pointer to the following\nnode in the same bucket.\n\n```java\nstatic class Node\u003CK,V> {\n  final int hash;   \u002F\u002F cached spread hash of the key\n  final K key;\n  V value;\n  Node\u003CK,V> next;   \u002F\u002F chains entries in the same bucket\n}\n```\n\nOn `put`, the key's hash picks a bucket index; the entry is stored there (or\nappended to the chain on a collision). On `get`, the same index is computed and\nthe chain is scanned with `equals` to find the matching key. With a good hash,\noperations are average **O(1)**; the array is resized as it fills to keep chains\nshort.\n",{"id":28,"difficulty":14,"q":29,"a":30},"hashing-spread-function","What is the spread (perturbation) function and why does HashMap apply it?","The bucket index is computed as `(n - 1) & hash`, which only keeps the **low\nbits** of the hash (since `n` is a power of two). If two keys differ only in\ntheir high bits, they'd collide. To mix high bits down into the low ones,\n`HashMap` applies a **spread** function before indexing.\n\n```java\nstatic final int hash(Object key) {\n  int h;\n  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n}\n```\n\nXORing the hash with its own upper 16 bits is cheap (one shift, one XOR) yet\nmeaningfully reduces collisions for hashes whose entropy lives in the high bits.\nNote a **null key** is mapped to hash `0`, which is why it always lands in\nbucket 0.\n",{"id":32,"difficulty":24,"q":33,"a":34},"index-calculation","How does HashMap convert a hash into a bucket index?","It uses **`index = (n - 1) & hash`**, where `n` is the table length. Because `n`\nis always a power of two, `n - 1` is a mask of all-ones in the low bits, so the\nbitwise `&` is equivalent to `hash % n` but far faster than a modulo.\n\n```java\nint n = 16;              \u002F\u002F table length, a power of two\nint idx = (n - 1) & hash; \u002F\u002F 15 & hash  ->  keeps the low 4 bits\n```\n\nThis is the whole reason capacity must be a power of two: the `&` trick only\ndistributes entries evenly across all buckets when `n - 1` is a clean low-bit\nmask. A non-power-of-two `n` would leave some buckets unreachable.\n",{"id":36,"difficulty":14,"q":37,"a":38},"why-power-of-two","Why must HashMap capacity always be a power of two?","Two reasons, both about the `(n - 1) & hash` indexing. First, **speed**: a\nbitwise AND replaces an expensive modulo. Second, **distribution**: when `n` is\na power of two, `n - 1` is `0b...0111…1`, so the AND keeps a contiguous block of\nlow bits and every bucket is reachable. If `n` weren't a power of two, the mask\nwould have gaps and some buckets could never be hit.\n\n```java\n\u002F\u002F HashMap rounds any requested capacity up to the next power of two\nnew HashMap\u003C>(10);  \u002F\u002F actual table capacity becomes 16\nnew HashMap\u003C>(20);  \u002F\u002F becomes 32\n```\n\nThe constructor runs `tableSizeFor` to round your requested initial capacity up\nto the nearest power of two, so you can never actually create a non-power-of-two\ntable.\n",{"id":40,"difficulty":14,"q":41,"a":42},"put-flow","Walk through what happens on a put() call.","1. Compute the **spread hash** of the key.\n2. Compute the bucket index `(n - 1) & hash`.\n3. If the bucket is **empty**, drop the new node in.\n4. If occupied, **scan the chain**: a node matching by `hash` *and*\n   `equals` means an existing key — overwrite its value and return the old one.\n5. Otherwise **append** a new node; if the chain length crosses **8** (and the\n   table is ≥ 64), **treeify** the bucket.\n6. Increment `size`; if `size > threshold` (capacity × load factor), **resize**.\n\n```java\nmap.put(\"a\", 1);  \u002F\u002F new bucket\nmap.put(\"a\", 2);  \u002F\u002F same key -> value replaced, returns 1\nmap.put(\"b\", 3);  \u002F\u002F collision? append to chain\n```\n\nThe match step is why `equals` is essential: same bucket does **not** mean same\nkey — the chain must be walked and compared.\n",{"id":44,"difficulty":24,"q":45,"a":46},"get-flow","Walk through what happens on a get() call.","`get` mirrors `put`'s lookup half: spread the key's hash, compute the index, then\n**scan the bucket**. For each node it first compares the cached **hash** (a cheap\nint compare) and only then calls **`equals`** — the hash check short-circuits\nmost mismatches.\n\n```java\nV get(Object key) {\n  \u002F\u002F hash -> index -> walk chain\n  \u002F\u002F return node where node.hash == h && (k == node.key || k.equals(node.key))\n}\n```\n\nIn a linked-list bucket this is **O(chain length)**; in a treeified bucket it's\n**O(log n)**. A missing key returns `null` — which is indistinguishable from a\nkey mapped *to* `null`, so use `containsKey` when that difference matters.\n",{"id":48,"difficulty":24,"q":49,"a":50},"collision-handling","How does HashMap handle hash collisions?","When two keys map to the same bucket (a **collision**), `HashMap` chains them.\nOriginally every bucket is a **singly linked list** of `Node`s. As of Java 8,\nonce a single bucket's chain grows past a threshold the bucket is converted to a\n**red-black tree** so lookups within it stay logarithmic rather than linear.\n\n```text\nbucket[5] -> Node(k1) -> Node(k2) -> Node(k3)   \u002F\u002F linked list\n\n\u002F\u002F after treeification:\nbucket[5] -> (balanced red-black tree of TreeNodes)\n```\n\nSo a collision degrades the *bucket* from O(1) toward O(log n) at worst (with a\nreasonable `hashCode`), not toward O(n). Distinct keys can collide even with\nperfect hashCodes simply because many hashes fold into the same small index.\n",{"id":52,"difficulty":14,"q":53,"a":54},"treeification","What is treeification and when does it happen?","**Treeification** converts a bucket from a linked list to a **red-black tree**\nwhen it gets too long, bounding worst-case lookups at **O(log n)** instead of\nO(n). Two conditions must both hold:\n\n| Constant | Value | Meaning |\n| -------- | ----- | ------- |\n| `TREEIFY_THRESHOLD` | 8 | chain length that triggers treeify |\n| `MIN_TREEIFY_CAPACITY` | 64 | table must be at least this big |\n| `UNTREEIFY_THRESHOLD` | 6 | tree shrinks back to a list at\u002Fbelow this |\n\n```java\n\u002F\u002F if a bucket reaches 8 nodes but table \u003C 64,\n\u002F\u002F HashMap resizes instead of treeifying\n```\n\nIf the table is smaller than 64, a long chain usually means the table is just too\nsmall, so `HashMap` **resizes** rather than treeifying. The gap between 8 and 6\n(hysteresis) avoids flip-flopping between tree and list on repeated add\u002Fremove.\n",{"id":56,"difficulty":24,"q":57,"a":58},"load-factor","What are the default initial capacity and load factor, and what do they mean?","The defaults are **initial capacity 16** and **load factor 0.75**. The load\nfactor is the fullness fraction at which the table grows: the **threshold** =\ncapacity × load factor, so a default map resizes when it holds more than\n`16 × 0.75 = 12` entries.\n\n```java\nnew HashMap\u003C>();            \u002F\u002F capacity 16, threshold 12\nnew HashMap\u003C>(32, 0.5f);    \u002F\u002F capacity 32, threshold 16\n```\n\n`0.75` is a deliberate **time\u002Fspace trade-off**: lower factors waste memory but\nreduce collisions; higher factors save memory but lengthen chains. If you know\nthe final size, presize with `new HashMap\u003C>(expected \u002F 0.75 + 1)` to avoid\nrepeated resizes.\n",{"id":60,"difficulty":14,"q":61,"a":62},"resizing-rehash","What happens during a HashMap resize?","When `size` exceeds the threshold, `HashMap` **doubles the capacity** (a new\npower-of-two table) and **rehashes** every entry into the larger table. Doubling\nkeeps the power-of-two invariant so `(n - 1) & hash` stays valid.\n\n```java\n\u002F\u002F Java 8 split trick: with capacity doubled, an entry either stays\n\u002F\u002F at index i, or moves to index i + oldCapacity — decided by one bit:\nif ((node.hash & oldCapacity) == 0)  \u002F\u002F stays in \"low\" bucket\nelse                                  \u002F\u002F moves to \"high\" bucket\n```\n\nBecause only the **single new high bit** of the hash decides where an entry\ngoes, Java 8 splits each old bucket's chain into exactly two ordered sub-chains\nwithout recomputing indexes from scratch. Resizing is **O(n)** and rebuilds the\ntable, which is why presizing matters for large maps.\n",{"id":64,"difficulty":14,"q":65,"a":66},"hashcode-equals-contract","What is the hashCode\u002Fequals contract and why must HashMap keys honor it?","The contract: if `a.equals(b)` then `a.hashCode() == b.hashCode()`\n**must** hold (the reverse need not — unequal objects may share a hash). `equals`\nmust also be reflexive, symmetric, transitive, and consistent.\n\n```java\nclass Point {\n  int x, y;\n  @Override public boolean equals(Object o) { \u002F* compare x,y *\u002F }\n  @Override public int hashCode() { return Objects.hash(x, y); }\n}\n```\n\n`HashMap` relies on this directly: it uses `hashCode` to find the **bucket** and\n`equals` to find the **key** within it. Override only one and a key you stored\nbecomes unfindable — `get` lands in the wrong bucket (or the right bucket but\nfails the `equals` check) and returns `null`. Always override the **two\ntogether**.\n",{"id":68,"difficulty":14,"q":69,"a":70},"bad-hashcode-on","How can a bad hashCode degrade HashMap to O(n)?","If `hashCode` returns the **same value for every key** (e.g. `return 42;` or\n`return 0;`), every entry hashes to one bucket. That single bucket becomes one\ngiant chain, so `get`\u002F`put` must scan it linearly — **O(n)** per operation,\ndefeating the whole point of a hash map.\n\n```java\nclass Bad {\n  @Override public int hashCode() { return 1; } \u002F\u002F all collide!\n}\n\u002F\u002F 10,000 Bad keys -> one bucket of 10,000 nodes -> O(n) lookups\n```\n\nTreeification softens but doesn't fix this: the bucket becomes a red-black tree,\nimproving to **O(log n)** — and only if the keys are also `Comparable`, otherwise\nit falls back to a list. A well-distributed `hashCode` is the real cure.\n",{"id":72,"difficulty":24,"q":73,"a":74},"null-keys-values","Can a HashMap store null keys and null values?","Yes — `HashMap` allows **one null key** and **any number of null values**. The\nnull key is special-cased to hash `0`, so it always sits in **bucket 0**.\n\n```java\nMap\u003CString, String> m = new HashMap\u003C>();\nm.put(null, \"x\");   \u002F\u002F legal — single null key\nm.put(\"a\", null);   \u002F\u002F legal — null value\nm.get(\"missing\");   \u002F\u002F null — but is the key absent or mapped to null?\nSystem.out.println(m.containsKey(\"a\")); \u002F\u002F true, even though value is null\n```\n\nThe ambiguity of a `null` return is the catch: `get` returns `null` both for an\nabsent key and a key mapped to `null`. Use **`containsKey`** to tell them apart.\nContrast with `Hashtable` and `ConcurrentHashMap`, which forbid nulls entirely.\n",{"id":76,"difficulty":14,"q":77,"a":78},"fail-fast-iterators","What are fail-fast iterators and ConcurrentModificationException?","`HashMap`'s iterators are **fail-fast**: they track a `modCount` and throw\n**`ConcurrentModificationException`** if the map is structurally modified (add or\nremove) during iteration through any path other than the iterator itself.\n\n```java\nfor (String k : map.keySet()) {\n  if (k.startsWith(\"x\")) map.remove(k);  \u002F\u002F throws CME\n}\n\n\u002F\u002F safe: remove through the iterator\nIterator\u003CString> it = map.keySet().iterator();\nwhile (it.hasNext()) { if (it.next().startsWith(\"x\")) it.remove(); }\n```\n\nFail-fast is a **best-effort bug detector**, not a guarantee — it's specified as\n\"throws on a best-effort basis,\" so never write logic that depends on the\nexception. To mutate while iterating, use `iterator.remove()`, collect-then-\nremove, or `removeIf`.\n",{"id":80,"difficulty":14,"q":81,"a":82},"hashmap-not-thread-safe","Why is HashMap not thread-safe, and what was the infinite-loop bug?","`HashMap` has **no synchronization**, so concurrent `put`s can interleave and\ncorrupt the table — lost updates, wrong `size`, or a thread seeing a half-built\nstate. The most infamous case was an **infinite loop on resize** in pre-Java-8\nJDKs.\n\n```text\nold resize() reversed each bucket's chain while transferring it;\ntwo threads resizing at once could splice A -> B -> A into a cycle,\nso a later get() would spin forever at 100% CPU.\n```\n\nJava 8 changed resize to preserve chain order (the low\u002Fhigh split), which removed\n*that* specific cycle — but `HashMap` is **still not thread-safe** and can lose\ndata or throw under concurrency. For shared maps use **`ConcurrentHashMap`**;\n`Collections.synchronizedMap` works but locks the whole map.\n",{"id":84,"difficulty":24,"q":85,"a":86},"immutable-keys","Why should HashMap keys be immutable?","A key's hash is **computed when it's inserted** and used to pick its bucket. If\nyou then mutate a field that affects `hashCode`\u002F`equals`, the key's new hash\npoints to a **different bucket** than where it's actually stored — so the map can\nno longer find it.\n\n```java\nSet\u003CList\u003CInteger>> s = new HashSet\u003C>();\nList\u003CInteger> key = new ArrayList\u003C>(List.of(1, 2));\ns.add(key);\nkey.add(3);                     \u002F\u002F mutates the key's hashCode!\nSystem.out.println(s.contains(key)); \u002F\u002F false — lost in the wrong bucket\n```\n\nThe entry becomes a \"ghost\": present in the map but unreachable by `get` or\n`contains`, and possibly duplicated on re-insert. Prefer **immutable keys**\n(`String`, `Integer`, records, frozen value objects) so the hash can never drift.\n",{"id":88,"difficulty":24,"q":89,"a":90},"hashmap-vs-linkedhashmap-vs-treemap","What is the difference between HashMap, LinkedHashMap and TreeMap?","All implement `Map`, but they differ in **ordering** and **complexity**:\n\n| Map | Ordering | get\u002Fput | Backing structure |\n| --- | -------- | ------- | ----------------- |\n| `HashMap` | **none** (arbitrary) | O(1) avg | hash table |\n| `LinkedHashMap` | **insertion** (or access) order | O(1) avg | hash table + linked list |\n| `TreeMap` | **sorted** by key | O(log n) | red-black tree |\n\n```java\nnew HashMap\u003C>();        \u002F\u002F fastest, no order guarantee\nnew LinkedHashMap\u003C>();  \u002F\u002F predictable iteration order\nnew TreeMap\u003C>();        \u002F\u002F keys come out sorted; needs Comparable\u002FComparator\n```\n\nPick `HashMap` by default, `LinkedHashMap` when you need stable iteration order\nor an **LRU cache** (access-order mode + `removeEldestEntry`), and `TreeMap` when\nyou need sorted keys or range queries (`firstKey`, `headMap`, `subMap`).\n",{"id":92,"difficulty":14,"q":93,"a":94},"hashmap-vs-hashtable-vs-chm","What is the difference between HashMap, Hashtable and ConcurrentHashMap?","| Feature | `HashMap` | `Hashtable` | `ConcurrentHashMap` |\n| ------- | --------- | ----------- | ------------------- |\n| Thread-safe | no | yes (whole-method `synchronized`) | yes (fine-grained) |\n| Null key\u002Fvalues | 1 null key, null values | none | none |\n| Locking | — | one lock for the whole table | per-bucket \u002F CAS |\n| Status | preferred single-thread | **legacy** | preferred concurrent |\n\n```java\nMap\u003CString,Integer> m = new ConcurrentHashMap\u003C>();\nm.compute(\"k\", (k, v) -> v == null ? 1 : v + 1); \u002F\u002F atomic, no external lock\n```\n\n`Hashtable` is a legacy class that locks every method, so it serializes all\naccess — slow and effectively retired. **`ConcurrentHashMap`** locks at the\nbucket\u002Fbin level (with CAS for hot paths), so many threads can write to\ndifferent buckets in parallel. Its iterators are **weakly consistent** (no\n`ConcurrentModificationException`).\n",{"id":96,"difficulty":14,"q":97,"a":98},"chm-internals","How does ConcurrentHashMap achieve thread safety without a global lock?","Modern `ConcurrentHashMap` (Java 8+) abandoned the old segment design for the\nsame `Node[]` table as `HashMap`, plus **per-bin locking** and **CAS**. An empty\nbin is filled with a lock-free **compare-and-swap**; a non-empty bin is updated\nunder a `synchronized` block on the bin's **first node** — so contention is\nlimited to the keys colliding in that one bin.\n\n```java\nConcurrentHashMap\u003CString,Long> counts = new ConcurrentHashMap\u003C>();\ncounts.merge(\"hit\", 1L, Long::sum);   \u002F\u002F atomic increment, thread-safe\n```\n\nReads are **non-blocking** (fields are `volatile`), so `get` never locks. Resizes\nare **cooperative**: multiple threads help transfer bins concurrently. This is\nwhy it scales far better than `Hashtable`'s single lock while still being fully\ncorrect.\n",{"id":100,"difficulty":14,"q":101,"a":102},"treenode-comparable","What happens when treeified keys are not Comparable?","A treeified bucket is a **red-black tree**, which normally orders nodes by the\nkey's `compareTo`. If the keys don't implement **`Comparable`**, `HashMap` can't\nsort them, so it falls back to **comparing identity hash codes** and uses a\ntie-breaking routine (`tieBreakOrder`) to keep the tree balanced — searches then\neffectively scan rather than binary-search within ties.\n\n```java\n\u002F\u002F keys with equal hash but no Comparable order:\n\u002F\u002F tree falls back to System.identityHashCode() ordering for placement\n```\n\nThe practical takeaway: treeification still **bounds** worst case better than a\nplain list, but you only get the full **O(log n)** lookup benefit when colliding\nkeys are mutually `Comparable`. A good `hashCode` (so buckets never treeify) is\nstill the best defense.\n",{"id":104,"difficulty":105,"q":106,"a":107},"hashset-backing","easy","How is HashSet implemented on top of HashMap?","`HashSet` is a thin wrapper around a `HashMap`: it stores each element as a\n**key**, mapping every key to a single shared dummy value (a constant `PRESENT`\nobject). So all of `HashSet`'s behavior — hashing, buckets, load factor,\ntreeification — is just `HashMap`'s.\n\n```java\nprivate static final Object PRESENT = new Object();\npublic boolean add(E e) {\n  return map.put(e, PRESENT) == null;  \u002F\u002F null means key was new\n}\n```\n\nThis is why `HashSet` has the same **O(1)** average operations, the same\n**null-element-allowed** rule, and the same requirement that elements have a\nproper `hashCode`\u002F`equals`. `LinkedHashSet` and `TreeSet` likewise wrap\n`LinkedHashMap` and `TreeMap`.\n",{"id":109,"difficulty":24,"q":110,"a":111},"presize-hashmap","How should you size a HashMap to avoid resizing?","Each resize is an **O(n) rehash**, so for a map you'll fill to a known size,\npresize it. Because resize triggers at `capacity × 0.75`, request an initial\ncapacity of about **expected \u002F 0.75 + 1** so the table never crosses its\nthreshold during loading.\n\n```java\nint expected = 1000;\nMap\u003CString,Integer> m = new HashMap\u003C>((int)(expected \u002F 0.75f) + 1);\n\n\u002F\u002F Java 19+: clearer intent, does the math for you\nMap\u003CString,Integer> m2 = HashMap.newHashMap(expected);\n```\n\nPassing the raw expected size (`new HashMap\u003C>(1000)`) is a common mistake — it\nrounds to capacity 1024 but still resizes at 768 entries. Java 19's\n`HashMap.newHashMap(n)` factory expresses \"I will hold n entries\" directly.\n",22,null,{"description":11},"Java HashMap internals interview questions — buckets and hashing, load factor and resizing, treeification, collision handling, hashCode\u002Fequals contract, and HashMap vs ConcurrentHashMap vs Hashtable.","java\u002Fcollections\u002Fhashmap-internals","HashMap Internals","Collections","collections","2026-06-20","aUEBoW19JTqJbSySvGUbVVFn43b_ZfXDBSwzWvGsAvw",[123,127,128,132,136],{"subtopic":124,"path":125,"order":126},"Lists, Maps & Sets","\u002Fjava\u002Fcollections\u002Flist-map-set",1,{"subtopic":117,"path":20,"order":12},{"subtopic":129,"path":130,"order":131},"Set Implementations","\u002Fjava\u002Fcollections\u002Fset-implementations",3,{"subtopic":133,"path":134,"order":135},"Queue & Deque","\u002Fjava\u002Fcollections\u002Fqueue-deque",4,{"subtopic":137,"path":138,"order":139},"Comparable & Comparator","\u002Fjava\u002Fcollections\u002Fcomparable-comparator",5,{"path":141,"title":142},"\u002Fblog\u002Fjava-hashmap-internals","How Java HashMap Works Internally — Buckets, Hashing, Resizing & Treeification",1782244116181]