Companion code for the ankurm.com post "Top 40 Java Collections Interview Questions (HashMap Internals to Fail-Fast Iterators)". Verified against JDK 25's own HashMap source (TREEIFY_THRESHOLD=8, UNTREEIFY_THRESHOLD=6, MIN_TREEIFY_CAPACITY=64), with every claim backed by a committed docs/output/*.txt transcript from a real run. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YXCrLgRKFgCh9RHKW8xaqJ
5.8 KiB
java-interview-lab
Runnable companion code for ankurm.com's Java interview-prep article series. Every number, stack trace, and console line quoted in those articles comes from a file committed somewhere under this repository — nothing is retyped or paraphrased from memory.
One module per article. This repository currently holds:
| Module | Article | Status |
|---|---|---|
collections |
Top 40 Java Collections Interview Questions (HashMap Internals to Fail-Fast Iterators) | this commit |
collections module
HashMap/HashSet/ArrayList internals, verified against the real JDK 25 java.util.HashMap
source (not against its javadoc prose) and reproduced with real, runnable code.
Versions
| Component | Version | Verified against |
|---|---|---|
| JDK | 25.0.4.1+1 (Temurin, LTS) | java -version; java.util.HashMap source extracted from this JDK's own lib/src.zip |
| JUnit | 5.11.0 | org.junit.jupiter:junit-jupiter on Maven Central |
| Maven | 3.9.11 | mvn -version |
The three constants this article leans on hardest (TREEIFY_THRESHOLD = 8,
UNTREEIFY_THRESHOLD = 6, MIN_TREEIFY_CAPACITY = 64) were read directly out of
java.util.HashMap's source for this exact JDK build, not copied from a blog post or an older
javadoc. See collections/src/main/java/.../BucketInspector.java for the reflective dump that
proves them at runtime.
Quickstart
cd collections
export JAVA_HOME=/path/to/jdk-25 # must be JDK 25 or newer
export PATH="$JAVA_HOME/bin:$PATH"
mvn test # runs everything, including the HashMap-internals demos
The first mvn invocation in a fresh environment must run online (not -o) once, so the
compiler/surefire plugins populate the local repository cache; -o works for every run after
that. This is a Maven quirk, not specific to this module.
Run one scenario at a time with ./scripts/run.sh <name> (add-opens-fail, add-opens-ok,
treeify, cme, eqhash, bucket — see the script for what each prints). Regenerate
every committed transcript in docs/output/ with ./scripts/run-all.sh.
No HTTP endpoints in this module — every demo is a plain Java main() method or a
JUnit test; there is nothing to start as a server. The diagnostic tool is
BucketInspector.dump(HashMap), a reflective helper, not an endpoint.
The one flag that matters: --add-opens
BucketInspector reflects into java.util.HashMap's private table field to show the real
bucket layout. Since Java 9, that throws InaccessibleObjectException unless the JVM is
started with:
--add-opens java.base/java.util=ALL-UNNAMED
pom.xml's surefire configuration sets this for mvn test automatically. Running
ReflectionAddOpensDemo directly with plain java -cp ... reproduces the crash on purpose
— see docs/output/01-reflection-without-add-opens.txt.
Source layout
collections/
├── pom.xml
├── scripts/
│ ├── run.sh one named scenario at a time
│ └── run-all.sh regenerates every file in docs/output/
├── src/main/java/com/ankurm/interviewlab/collections/
│ ├── BucketInspector.java reflective bucket/bin dump (needs --add-opens)
│ ├── CollidingKey.java hashCode()==1 for every instance - forces one bucket
│ ├── ReflectionAddOpensDemo.java standalone entry point for the --add-opens demo
│ └── eqhash/
│ ├── EqualsOnlyPoint.java equals() overridden, hashCode() is NOT - the broken case
│ ├── CorrectPoint.java both overridden consistently - the fixed case
│ └── MutableKey.java a key whose hash-relevant field can change post-insertion
├── src/test/java/com/ankurm/interviewlab/collections/
│ ├── support/Transcript.java writes docs/output/NN-*.txt while the test asserts the same numbers
│ ├── BucketLayoutTest.java
│ ├── TreeifyTriggerTest.java
│ ├── ConcurrentModificationTest.java
│ └── EqualsHashCodeContractTest.java
└── docs/output/*.txt captured real output - see index below
Index of captured output (docs/output/)
| File | What it proves |
|---|---|
01-reflection-without-add-opens.txt |
InaccessibleObjectException, uncaught, crashing the JVM — real stack trace |
02-reflection-with-add-opens.txt |
Same code, with the flag: a real bucket dump of a 2-entry HashMap |
03-bucket-layout-small-map.txt |
Real bucket indices for 6 string keys at default capacity 16 |
04-treeify-trigger.txt |
Capacity doubling 16→32→64 purely from treeifyBin()'s own resize calls, then a real Node→TreeNode conversion at insert #11 |
05-cme-classic-reproduction.txt |
A real ConcurrentModificationException, full stack trace, from for-each + Collection.remove() |
06-cme-silent-non-reproduction.txt |
The documented case where the SAME kind of mid-loop removal does not throw |
07-cme-concurrenthashmap-no-throw.txt |
ConcurrentHashMap's weakly-consistent iterator mutated during iteration, never throws |
08-iterator-remove-safe.txt |
Iterator.remove() used correctly — no exception, correct result |
09-equals-hashcode-broken-contract.txt |
equals() without hashCode(): a real HashSet failing to deduplicate two equal objects |
10-mutable-key-lost-entry.txt |
A key mutated after insertion: the entry is unreachable by lookup but still physically present |
All ten are regenerated by one command (./scripts/run-all.sh); eight of the ten (03 through 10)
are generated by a JUnit test that also asserts the same numbers it prints, so a figure in the
article that stops being true fails the build, not just the next blog audit.
License
MIT — see LICENSE.