# 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`](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 ```bash 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 ` (`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](LICENSE).