Skip to main content

English + German

English stays on the page. Click the button to show the German text under each question.

Collections and Streams

If the interviewer draws a HashMap on the board, they want buckets, equals, and fail-fast — not “it stores key-value pairs”.

Deutsch

Collections und Streams

Wenn Interviewer eine HashMap an die Tafel zeichnen, wollen sie Buckets, equals und fail-fast — nicht „die speichert Key-Value-Paare“.

1. ArrayList vs LinkedList vs array?

Deutsch

ArrayList vs LinkedList vs Array?

Level: Junior · Listen for: random access vs node allocation; almost always ArrayList

Niveau: Junior · Darauf hören sie: Random Access vs. Node-Allokation; fast immer ArrayList

Model answer

ArrayList is a resizable array: get is O(1), append is amortized O(1), insert at the front is O(n). It is the default list.

LinkedList is a doubly linked list. Middle insert is O(1) only if you already have the node. Finding the index is O(n), and each node is a separate object, so it is usually slower and more garbage.

A raw array is fixed size and slightly cheaper. Use it for primitives or tight internals, not as a public API.

Follow-ups

  • When would you still pick LinkedList? (almost never; a Deque maybe)
  • Why is array.length a field and list.size() a method?

Trap: “LinkedList is faster for inserts” without the cursor caveat.

Memory sentence: Default to ArrayList; linked lists rarely win in real JVMs.

Musterantwort

ArrayList ist ein Array, das mitwachsen kann: get ist O(1), Anhängen ist amortisiert O(1), Einfügen vorne ist O(n). Das ist die Default-List.

LinkedList ist eine doppelt verkettete Liste. Einfügen in der Mitte ist O(1) nur, wenn du den Node schon hast. Den Index finden ist O(n), und jeder Node ist ein eigenes Objekt — deshalb meist langsamer und mehr Garbage.

Ein Array hat feste Größe und ist etwas günstiger. Nimm es für Primitives oder sehr enge interne Stellen, nicht als öffentliche API.

Nachfragen

  • Wann würdest du trotzdem LinkedList nehmen? (fast nie; vielleicht eine Deque)
  • Warum ist array.length ein Feld und list.size() eine Methode?

Falle: „LinkedList ist schneller beim Einfügen“ ohne den Haken, dass du schon am Node stehen musst.

Merksatz: Default ist ArrayList; verkettete Listen gewinnen in echten JVMs selten.

2. How does HashMap work internally?

Deutsch

Wie funktioniert HashMap intern?

Level: Mid · Listen for: hash, bucket, linked list then tree, resize, equals

Niveau: Mid · Darauf hören sie: Hash, Bucket, Linked List dann Tree, Resize, equals

Model answer

put computes hashCode, mixes it, and picks a bucket. If the bucket is empty, the entry sits there. On collision, entries form a list; if a bin grows past a threshold and the table is large enough, Java treeifies it into a red-black tree (since Java 8).

Lookup uses hash first, then equals. Resize doubles capacity when the load factor (default 0.75) is exceeded, and entries are redistributed.

Keys must have a stable equals/hashCode. null keys are allowed in HashMap (one of them), not in ConcurrentHashMap.

Follow-ups

  • Why mix the hash (XOR with shifted bits)?
  • What changed in Java 8 treeification?

Trap: “HashMap is O(1)” as an absolute. It is expected constant time if the hash is decent.

Memory sentence: Hash picks the bucket; equals confirms the key; collisions list, then tree.

Musterantwort

put berechnet hashCode, mixt die Bits und wählt einen Bucket. Ist der Bucket leer, liegt der Entry dort. Bei einer Kollision bilden die Entries eine Liste; wächst ein Bin über einen Schwellwert und ist die Tabelle groß genug, treeifiziert Java sie zu einem Red-Black Tree (seit Java 8).

Die Suche nutzt zuerst den Hash, dann equals. Resize verdoppelt die Capacity, wenn der Load Factor (Default 0.75) überschritten ist, und die Entries werden neu verteilt.

Keys brauchen ein stabiles equals/hashCode. null-Keys sind in HashMap erlaubt (einer), in ConcurrentHashMap nicht.

Nachfragen

  • Warum den Hash mixen (XOR mit verschobenen Bits)?
  • Was hat sich bei der Treeification in Java 8 geändert?

Falle: „HashMap ist O(1)“ als absolute Aussage. Es ist erwartete konstante Zeit, wenn der Hash taugt.

Merksatz: Der Hash wählt den Bucket; equals bestätigt den Key; Kollisionen erst Liste, dann Tree.

3. HashMap vs Hashtable vs ConcurrentHashMap?

Deutsch

HashMap vs Hashtable vs ConcurrentHashMap?

Level: Mid · Listen for: synchronization granularity; Hashtable is legacy

Niveau: Mid · Darauf hören sie: Granularität der Synchronisierung; Hashtable ist Legacy

Model answer

Hashtable is a legacy synchronized map: every method locks the whole table. Do not use it.

HashMap is unsynchronized. Concurrent reads/writes throw ConcurrentModificationException or corrupt the table (historically could even livelock on resize).

ConcurrentHashMap allows concurrent reads and segmented/bin-level updates. It does not allow null keys or values. Iteration is weakly consistent: it does not throw CME, and it may or may not see later writes.

For a compound check-then-act (if (!map.containsKey) map.put), you still need putIfAbsent / computeIfAbsent, not two separate calls.

Follow-ups

  • Collections.synchronizedMap vs ConcurrentHashMap?
  • Why no nulls in ConcurrentHashMap?

Trap: wrapping HashMap in synchronizedMap and still iterating without a lock.

Memory sentence: ConcurrentHashMap for shared maps; Hashtable is history; atomic methods for check-then-act.

Musterantwort

Hashtable ist eine Legacy-Map mit Synchronisierung: jede Methode lockt die ganze Tabelle. Nicht verwenden.

HashMap ist unsynchronisiert. Gleichzeitige Reads und Writes werfen ConcurrentModificationException oder korrumpieren die Tabelle (historisch konnte Resize sogar in einen Livelock laufen).

ConcurrentHashMap erlaubt gleichzeitige Reads und Updates auf Segment-/Bin-Ebene. null-Keys und -Values sind nicht erlaubt. Iteration ist weakly consistent: sie wirft kein CME, und spätere Writes sieht sie vielleicht — oder eben nicht.

Für ein zusammengesetztes Check-then-Act (if (!map.containsKey) map.put) brauchst du trotzdem putIfAbsent / computeIfAbsent, nicht zwei getrennte Aufrufe.

Nachfragen

  • Collections.synchronizedMap vs ConcurrentHashMap?
  • Warum keine Nulls in ConcurrentHashMap?

Falle: HashMap in synchronizedMap wrappen und trotzdem ohne Lock iterieren.

Merksatz: ConcurrentHashMap für geteilte Maps; Hashtable ist Geschichte; atomare Methoden für Check-then-Act.

4. HashSet vs LinkedHashSet vs TreeSet?

Deutsch

HashSet vs LinkedHashSet vs TreeSet?

Level: Junior · Listen for: uniqueness via map; order guarantees

Niveau: Junior · Darauf hören sie: Eindeutigkeit über eine Map; Ordnungsgarantien

Model answer

HashSet is a HashMap with dummy values. No order. O(1) expected add/contains.

LinkedHashSet keeps insertion order (actually a linked map).

TreeSet is a TreeMap: sorted by Comparable or Comparator, O(log n), no hash. Equality for uniqueness is compare == 0, which must match equals.

Follow-ups

  • What happens if two elements compare as 0 but equals is false?
  • When do you want a LinkedHashMap with access order? (LRU-ish caches)

Trap: expecting HashSet to iterate in insertion order.

Memory sentence: Hash set is unordered uniqueness; linked preserves insert order; tree is sorted.

Musterantwort

HashSet ist eine HashMap mit Dummy-Values. Keine Ordnung. Erwartetes O(1) für add/contains.

LinkedHashSet hält die Einfügereihenfolge (eigentlich eine Linked Map).

TreeSet ist eine TreeMap: sortiert nach Comparable oder Comparator, O(log n), kein Hash. Gleichheit für Eindeutigkeit ist compare == 0, und das muss zu equals passen.

Nachfragen

  • Was passiert, wenn zwei Elemente als 0 vergleichen, equals aber false ist?
  • Wann willst du eine LinkedHashMap mit Access Order? (LRU-artige Caches)

Falle: von HashSet Einfügereihenfolge beim Iterieren erwarten.

Merksatz: Hash-Set ist ungeordnete Eindeutigkeit; linked hält die Einfügereihenfolge; Tree ist sortiert.

5. Fail-fast vs fail-safe iterators?

Deutsch

Fail-fast vs. fail-safe Iteratoren?

Level: Mid · Listen for: modCount; weakly consistent concurrent collections

Niveau: Mid · Darauf hören sie: modCount; weakly consistent Concurrent Collections

Model answer

ArrayList/HashMap iterators are fail-fast: they watch modCount. If the collection is structurally changed outside that iterator, next() throws ConcurrentModificationException. The check is best-effort, not a lock.

remove() on the iterator itself is allowed because it updates modCount.

Concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList) are weakly consistent / snapshot-ish: they do not throw CME. Copy-on-write copies the array on each mutation — great for rare writes, many reads; terrible for frequent writes.

Follow-ups

  • Why can fail-fast miss a race between two threads?
  • When is CopyOnWriteArrayList the right tool?

Trap: catching ConcurrentModificationException as normal control flow.

Memory sentence: Fail-fast iterators detect concurrent structural change; concurrent collections do not promise a live snapshot.

Musterantwort

Iteratoren von ArrayList/HashMap sind fail-fast: sie beobachten modCount. Wird die Collection außerhalb dieses Iterators strukturell geändert, wirft next() eine ConcurrentModificationException. Der Check ist Best-Effort, kein Lock.

remove() auf dem Iterator selbst ist erlaubt, weil es modCount aktualisiert.

Concurrent Collections (ConcurrentHashMap, CopyOnWriteArrayList) sind weakly consistent / Snapshot-artig: sie werfen kein CME. Copy-on-Write kopiert das Array bei jeder Mutation — gut bei seltenen Writes und vielen Reads; schlecht bei häufigen Writes.

Nachfragen

  • Warum kann fail-fast eine Race Condition zwischen zwei Threads verpassen?
  • Wann ist CopyOnWriteArrayList das richtige Werkzeug?

Falle: ConcurrentModificationException als normalen Control Flow fangen.

Merksatz: Fail-fast-Iteratoren erkennen gleichzeitige strukturelle Änderungen; Concurrent Collections versprechen kein Live-Snapshot.

6. Why does changing a key after insert break a HashMap?

Deutsch

Warum geht eine HashMap kaputt, wenn du den Key nach dem Insert änderst?

Level: Mid · Listen for: bucket computed at insert time

Niveau: Mid · Darauf hören sie: Bucket wird beim Insert berechnet

Model answer

The bucket is chosen from the key’s hash at insert. If you mutate a field that hashCode uses, the key now hashes elsewhere. get looks in the new bucket, does not find it, and the old entry is stranded. You can also get two keys that equals each other in different buckets.

Use immutable keys: String, records of immutable fields, IDs that do not change.

This is the same reason @Data on a JPA entity used as a map key is dangerous: the id appears after persist.

Follow-ups

  • Can you use a mutable key if you never mutate it after insert? (yes, but brittle)
  • IdentityHashMap?

Trap: “the map is corrupted by GC” — no, it is a hash/equals contract bug.

Memory sentence: Map keys must not change their hash after insert.

Musterantwort

Der Bucket wird aus dem Hash des Keys beim Insert gewählt. Mutierst du ein Feld, das hashCode nutzt, hasht der Key danach woanders. get schaut im neuen Bucket, findet ihn nicht, und der alte Entry strandet. Du kannst auch zwei Keys bekommen, die sich per equals gleichen, aber in verschiedenen Buckets liegen.

Nimm immutable Keys: String, Records mit unveränderlichen Feldern, IDs, die sich nicht ändern.

Deshalb ist @Data auf einer JPA-Entity als Map-Key gefährlich: die id erscheint erst nach dem Persist.

Nachfragen

  • Darfst du einen mutablen Key nutzen, wenn du ihn nach dem Insert nie mutierst? (ja, aber anfällig)
  • IdentityHashMap?

Falle: „die Map wird durch den GC korrumpiert“ — nein, das ist ein Bug im Hash/equals-Vertrag.

Merksatz: Map-Keys dürfen ihren Hash nach dem Insert nicht ändern.

7. Intermediate vs terminal stream operations? Why lazy?

Deutsch

Intermediate vs. Terminal Stream Operations? Warum lazy?

Level: Mid · Listen for: pipeline, fusion, no work until terminal

Niveau: Mid · Darauf hören sie: Pipeline, Fusion, keine Arbeit bis zur Terminal Operation

Model answer

Intermediate ops (map, filter, flatMap, sorted) return a new stream and are lazy. Terminal ops (collect, forEach, reduce, count, findFirst) consume the stream and trigger work.

Laziness lets the pipeline skip work: filter(...).map(...).findFirst() can stop early. A stream can be consumed once.

sorted and some others are stateful; they must buffer. peek is for debugging, not business logic.

Follow-ups

  • What does count() do after map — does map run? (it can be optimized away)
  • Parallel streams: when do they hurt?

Trap: calling a terminal op and then using the same stream again.

Memory sentence: Streams do nothing until a terminal operation; then they are spent.

Musterantwort

Intermediate-Operationen (map, filter, flatMap, sorted) liefern einen neuen Stream und sind lazy. Terminal-Operationen (collect, forEach, reduce, count, findFirst) konsumieren den Stream und lösen die Arbeit aus.

So kann die Pipeline Arbeit überspringen: filter(...).map(...).findFirst() kann früh stoppen. Ein Stream lässt sich einmal konsumieren.

sorted und manche andere sind stateful; sie müssen puffern. peek ist zum Debuggen, nicht für Business-Logik.

Nachfragen

  • Was macht count() nach map — läuft map? (kann wegoptimiert werden)
  • Parallel Streams: wann schaden sie?

Falle: eine Terminal Operation aufrufen und denselben Stream danach nochmal nutzen.

Merksatz: Streams tun nichts, bis eine Terminal Operation kommt; danach sind sie verbraucht.

8. map vs flatMap?

Deutsch

map vs flatMap?

Level: Mid · Listen for: one-to-one vs one-to-many flattening

Niveau: Mid · Darauf hören sie: One-to-one vs. One-to-many Flattening

Model answer

map transforms each element to one value. flatMap transforms each element to a stream (or optional) and flattens the result.

orders.map(Order::getId) // Stream<Long>
orders.flatMap(o -> o.getLines().stream()) // Stream<Line>

The same idea exists on Optional: flatMap avoids Optional<Optional<T>>.

In Spring Data / JPA, this is also how you think about one-to-many fetches: nested collections flatten into a row stream, which is why joins duplicate parents.

Follow-ups

  • mapMulti in newer Java?
  • Why flatMap on Optional instead of map?

Trap: map that returns a list, then wondering why you have Stream<List<T>>.

Memory sentence: map wraps; flatMap unwraps one level.

Musterantwort

map transformiert jedes Element zu einem Wert. flatMap transformiert jedes Element zu einem Stream (oder Optional) und flacht das Ergebnis ab.

Dieselbe Idee gibt es bei Optional: flatMap vermeidet Optional<Optional<T>>.

In Spring Data / JPA denkst du so auch über One-to-Many-Fetches: verschachtelte Collections flachen sich zu einem Row-Stream, deshalb duplizieren Joins die Parent-Zeilen.

Nachfragen

  • mapMulti in neuerem Java?
  • Warum flatMap auf Optional statt map?

Falle: map, das eine List zurückgibt — und dann wundern, warum du Stream<List<T>> hast.

Merksatz: map wrappt; flatMap unwrappt eine Ebene.

9. When should you not use streams?

Deutsch

Wann solltest du keine Streams nutzen?

Level: Mid · Listen for: checked exceptions, simple loops, performance, readability

Niveau: Mid · Darauf hören sie: Checked Exceptions, einfache Loops, Performance, Lesbarkeit

Model answer

Skip streams when:

  • the logic is a simple indexed loop
  • you need to break with complex control flow
  • the lambda would throw checked exceptions
  • you are mutating shared state inside forEach (side effects)
  • the team cannot read the pipeline in five seconds

Parallel streams are not a free speedup. They use the common ForkJoinPool, can starve HTTP threads, and only help CPU-heavy, well-splittable work on large datasets.

Follow-ups

  • Why is stream.forEach a bad substitute for a for-loop with checked exceptions?
  • How do you debug a pipeline?

Trap: rewriting every loop as a stream to look “modern”.

Memory sentence: Streams are for declaring a transformation; loops stay better for control flow and side effects.

Musterantwort

Lass Streams weg, wenn:

  • die Logik eine einfache Loop mit Index ist
  • du mit komplexem Control Flow mittendrin abbrechen musst
  • das Lambda checked Exceptions werfen würde
  • du in forEach geteilten State mutierst (Side Effects)
  • das Team die Pipeline nicht in fünf Sekunden lesen kann

Parallel Streams sind kein kostenloser Performance-Gewinn. Sie nutzen den gemeinsamen ForkJoinPool, können HTTP-Threads aushungern und helfen nur bei CPU-lastiger, gut splittbarer Arbeit auf großen Datenmengen.

Nachfragen

  • Warum ist stream.forEach ein schlechter Ersatz für eine for-Loop mit checked Exceptions?
  • Wie debuggst du eine Pipeline?

Falle: jede Loop als Stream umschreiben, um „modern“ auszusehen.

Merksatz: Streams deklarieren eine Transformation; Loops bleiben besser für Control Flow und Side Effects.

10. groupingBy, toMap, and key collisions?

Deutsch

groupingBy, toMap und Key-Kollisionen?

Level: Mid · Listen for: merge function; downstream collectors

Niveau: Mid · Darauf hören sie: Merge Function; Downstream Collectors

Model answer

Collectors.groupingBy(classifier) builds Map<K, List<T>>. Add a downstream collector for counts, sets, or nested grouping.

Collectors.toMap(key, value) throws if two elements share a key unless you pass a merge function: toMap(k, v, (a, b) -> b).

groupingBy is not concurrent unless you use groupingByConcurrent. The default map is a HashMap and is not thread-safe.

Follow-ups

  • How do you group and then reduce to the max per key?
  • collectingAndThen to wrap with unmodifiableMap?

Trap: toMap without a merge function on non-unique keys.

Memory sentence: toMap fails on duplicate keys unless you say how to merge.

Musterantwort

Collectors.groupingBy(classifier) baut Map<K, List<T>>. Häng einen Downstream Collector dran für Zählungen, Sets oder nested Grouping.

Collectors.toMap(key, value) wirft, wenn zwei Elemente denselben Key teilen — außer du übergibst eine Merge Function: toMap(k, v, (a, b) -> b).

groupingBy ist nicht concurrent, außer du nimmst groupingByConcurrent. Die Default-Map ist eine HashMap und nicht thread-safe.

Nachfragen

  • Wie gruppierst du und reduzierst dann auf das Maximum pro Key?
  • collectingAndThen, um mit unmodifiableMap zu wrappen?

Falle: toMap ohne Merge Function bei nicht-eindeutigen Keys.

Merksatz: toMap scheitert an doppelten Keys, außer du sagst, wie gemerged wird.

11. Why is HashMap not thread-safe, and what actually goes wrong?

Deutsch

Warum ist HashMap nicht thread-safe, und was geht wirklich schief?

Level: Mid · Listen for: lost updates, infinite loops historically, CME

Niveau: Mid · Darauf hören sie: Lost Updates, historisch Endlosschleifen, CME

Model answer

Two threads can put into the same bucket, and one write can disappear. Size counters drift. Iterators throw CME. In old JDKs, concurrent resize could create a cycle in a bucket and hang a get in an infinite loop. Modern JDKs treeify and do not loop forever, but you still lose data.

Fix: do not share the map, or use ConcurrentHashMap, or confine the map to one thread.

Follow-ups

  • Is get safe while another thread puts? (no, not for HashMap)
  • Does volatile HashMap help? (no)

Trap: “we only put at startup, then only get” — still publish the map safely (safe publication).

Memory sentence: Unsynchronized maps are not “mostly fine”; concurrent writes corrupt them.

Musterantwort

Zwei Threads können in denselben Bucket put aufrufen, und ein Write kann verschwinden. Size-Zähler laufen auseinander. Iteratoren werfen CME. In alten JDKs konnte gleichzeitiges Resize einen Zyklus in einem Bucket erzeugen und ein get in einer Endlosschleife hängen. Moderne JDKs treeifizieren und laufen nicht ewig, aber Daten gehen trotzdem verloren.

Dagegen: die Map nicht teilen, oder ConcurrentHashMap nutzen, oder die Map auf einen Thread beschränken.

Nachfragen

  • Ist get sicher, während ein anderer Thread put aufruft? (nein, nicht bei HashMap)
  • Hilft volatile HashMap? (nein)

Falle: „wir machen nur beim Startup put, danach nur get“ — die Map muss trotzdem sicher publiziert werden (Safe Publication).

Merksatz: Unsynchronisierte Maps sind nicht „meistens okay“; gleichzeitige Writes korrumpieren sie.

12. equals and hashCode for elements in a HashSet of entities?

Deutsch

equals und hashCode für Elemente in einem HashSet von Entities?

Level: Senior · Listen for: JPA identity; do not use mutable business fields

Niveau: Senior · Darauf hören sie: JPA Identity; keine mutablen Business-Felder

Model answer

A HashSet<Order> of JPA entities is a minefield. Before persist, id is null; after persist, id is set — hash changes, the set loses the entity.

Options that people use in practice:

  • equality by database id, but only after the entity is persistent, and never put transient entities in a hash-based collection
  • equality by a natural immutable business key (ISBN, ISO country code)
  • for parent-child Set<Line>, Hibernate often wants a stable equals; many teams use a generated UUID assigned in the constructor

Never include lazy associations in equals. That can trigger extra SQL or LazyInitializationException.

Follow-ups

  • Why does Lombok @Data on @Entity cause infinite recursion?
  • LinkedHashSet vs List for @OneToMany?

Trap: default equals (identity) vs business equals mixed in the same set.

Memory sentence: Entity equality must not change when the row gets an id.

Musterantwort

Ein HashSet<Order> von JPA-Entities ist ein Minenfeld. Vor dem Persist ist id null; danach ist id gesetzt — der Hash ändert sich, das Set verliert die Entity.

Optionen, die in der Praxis vorkommen:

  • Gleichheit über die Datenbank-id, aber nur, wenn die Entity persistent ist, und transiente Entities nie in eine hash-basierte Collection stecken
  • Gleichheit über einen natürlichen, unveränderlichen Business Key (ISBN, ISO-Ländercode)
  • für Parent-Child Set<Line> will Hibernate oft ein stabiles equals; viele Teams nutzen eine UUID, die im Konstruktor vergeben wird

Nimm niemals lazy Associations in equals auf. Das kann extra SQL oder LazyInitializationException auslösen.

Nachfragen

  • Warum verursacht Lombok @Data auf @Entity eine Endlosrekursion?
  • LinkedHashSet vs List für @OneToMany?

Falle: Default-equals (Identity) und Business-equals im selben Set mischen.

Merksatz: Entity-Gleichheit darf sich nicht ändern, wenn die Zeile eine id bekommt.

13. What is the difference between Collection, Collections, and Stream?

Deutsch

Was ist der Unterschied zwischen Collection, Collections und Stream?

Level: Junior · Listen for: API vs utility vs pipeline

Niveau: Junior · Darauf hören sie: API vs. Utility vs. Pipeline

Model answer

Collection is the root interface (List, Set, Queue). Collections is a utility class (unmodifiableList, sort, synchronizedMap). Stream is a pipeline over data, not a storage structure.

A stream does not store elements. A collection does. You can stream() a collection; you cannot treat a stream as a reusable list.

Follow-ups

  • Collections.emptyList() vs List.of()?
  • Why is List.of unmodifiable and null-hostile?

Trap: calling Collections.sort(stream) — sort is for lists.

Memory sentence: Collections store; streams compute; Collections is the helper class.

Musterantwort

Collection ist das oberste Interface (List, Set, Queue). Collections ist eine Utility-Klasse (unmodifiableList, sort, synchronizedMap). Stream ist eine Pipeline über Daten, keine Speicherstruktur.

Ein Stream speichert keine Elemente. Eine Collection schon. Du kannst auf einer Collection stream() aufrufen; einen Stream kannst du nicht wie eine wiederverwendbare List behandeln.

Nachfragen

  • Collections.emptyList() vs List.of()?
  • Warum ist List.of unmodifiable und null-hostile?

Falle: Collections.sort(stream) aufrufen — sort ist für Lists.

Merksatz: Collections speichern; Streams rechnen; Collections ist die Hilfsklasse.

14. How would you implement a simple LRU cache in Java?

Deutsch

Wie würdest du einen einfachen LRU-Cache in Java bauen?

Level: Senior · Listen for: LinkedHashMap access order, or Caffeine; eviction, concurrency

Niveau: Senior · Darauf hören sie: LinkedHashMap Access Order, oder Caffeine; Eviction, Concurrency

Model answer

The interview-size answer: subclass LinkedHashMap with access order and override removeEldestEntry when size > cap. That is a single-threaded LRU.

For production: Caffeine (or Spring Cache with Caffeine). You need max size, time-to-live, stats, and thread safety. ConcurrentHashMap alone is not LRU.

In Spring: @Cacheable plus a cache manager. Be explicit about keys, eviction, and cache stampede.

Follow-ups

  • What is a cache stampede?
  • Why not WeakHashMap as an LRU?

Trap: a handmade HashMap + list “cache” that is racy and leaks.

Memory sentence: Interview LRU is LinkedHashMap; production LRU is a real cache library.

Musterantwort

Die knappe Interview-Antwort: LinkedHashMap mit Access Order ableiten und removeEldestEntry überschreiben, wenn size > cap. Das ist ein Single-Thread-LRU.

Für Produktion: Caffeine (oder Spring Cache mit Caffeine). Du brauchst Max Size, Time-to-Live, Stats und Thread Safety. ConcurrentHashMap allein ist kein LRU.

In Spring: @Cacheable plus einen Cache Manager. Sei explizit bei Keys, Eviction und Cache Stampede.

Nachfragen

  • Was ist ein Cache Stampede?
  • Warum nicht WeakHashMap als LRU?

Falle: eine selbst gebaute HashMap plus List als „Cache“, die Race Conditions hat und leakt.

Merksatz: Interview-LRU ist LinkedHashMap; Produktions-LRU ist eine echte Cache-Library.