Guide contents

Lesson 68 of 71

Core Java, Generics, Collections, and Streams

Interview questions for senior and lead engineers on sections 1 to 6 of the course: language core, generics, lambdas and Optional, collections, streams, and testing. Each answer is short; the linked lesson has the full explanation and examples. Programs run on JDK 25.

Group Lessons
Types, strings, classes Types, Strings, Classes
Exceptions and contracts Exceptions, Object contracts, Immutability
Modern language Records and sealed types, Pattern matching, Enums and annotations, Modules and reflection
Generics Wildcards, Type erasure
Functional Lambdas, Optional and API design
Collections Lists and deques, HashMap, Sorted and heaps, Primitive collections
Streams Streams, Gatherers and cost
Testing JUnit, Mockito, Integration and concurrency testing

Types, strings, and classes

1. What happens on int overflow, and how is it detected?

It wraps silently in two's complement. Math.addExact and Math.multiplyExact throw ArithmeticException instead. Math.abs(Integer.MIN_VALUE) returns Integer.MIN_VALUE, a negative number, because the positive value does not fit.

2. Why is 0.1 + 0.2 != 0.3, and what are the other floating-point traps?

0.1 and 0.2 have no exact binary representation, so the sum is 0.30000000000000004. NaN != NaN; -0.0 == 0.0 is true, but Double.valueOf(-0.0).equals(0.0) is false. Money uses long minor units or BigDecimal.

3. Explain this output.

Java
public class IntegerCache {
    public static void main(String[] args) {
        Integer a = 127, b = 127;
        Integer c = 128, d = 128;
        System.out.println(a == b);
        System.out.println(c == d);
        System.out.println(c.equals(d));
    }
}
Output
true
false
true

== on wrappers compares identity. Integer.valueOf (used by autoboxing) caches -128 to 127 by default, so 127 boxes to the same object and 128 to two different objects. Compare wrappers with equals.

4. Spot the bug.

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

public class UnboxNull {
    public static void main(String[] args) {
        Map<String, Integer> stock = new HashMap<>();
        try {
            int n = stock.get("AAPL");
            System.out.println(n);
        } catch (NullPointerException e) {
            System.out.println("NPE: " + e.getMessage());
        }
        int safe = stock.getOrDefault("AAPL", 0);
        System.out.println(safe);
    }
}
Output
NPE: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
0

get returns null for a missing key, and unboxing null into int throws. The helpful NPE message names the expression. Use getOrDefault or keep the Integer and check it.

5. What are the rules for var?

Local variables only (including for and try-with-resources), with an initializer, inferred at compile time. It is not dynamic typing and cannot be used for fields, parameters, or return types. var x = null; does not compile.

6. How do switch expressions differ from switch statements?

Arrow labels do not fall through, a switch expression must be exhaustive, and a block arm returns its value with yield. Over an enum or sealed type that covers all cases, no default is needed.

7. Why are strings immutable, and what do compact strings change?

Immutability makes strings thread-safe, safe as map keys, and lets the hash code be cached. Compact strings (Java 9) store Latin-1 text in one byte per character and switch the whole string to UTF-16 when any character needs it.

8. When is s1 == s2 true for equal strings?

Only when both are the same object: literals and compile-time constants are interned, so two equal literals are ==. new String(...) and run-time concatenation create new objects; intern() returns the pooled one.

9. Is + on strings slow?

A single + expression is compiled with invokedynamic (StringConcatFactory) and is fine. += inside a loop copies the growing string each time and is quadratic; use one StringBuilder.

10. Follow-up: what does "😀".length() return?

2. length() counts UTF-16 code units, and code points above U+FFFF take a surrogate pair. Count user-visible characters with codePoints().count().

11. Name three other string traps.

trim removes only characters up to U+0020, strip is Unicode-aware. split and replaceAll compile a regex on every call; hot code keeps a compiled Pattern. The default charset is UTF-8 since Java 18, but code still passes charsets explicitly.

12. What is the initialization order of an object?

Superclass static init, then subclass static init (once per class). Per object: superclass constructor, then subclass field initializers and instance blocks in source order, then the subclass constructor body.

13. Spot the bug.

Java
public class CtorCall {
    static class Base {
        Base() { describe(); }
        void describe() { System.out.println("base"); }
    }

    static class Child extends Base {
        private String name = "child";
        @Override void describe() { System.out.println("name = " + name.length()); }
    }

    public static void main(String[] args) {
        try {
            new Child();
        } catch (NullPointerException e) {
            System.out.println("NPE: subclass field not yet initialized");
        }
    }
}
Output
NPE: subclass field not yet initialized

Base() calls an overridable method before Child's field initializers run, so name is still null. (A final field with a constant initializer would be inlined and hide the bug.) Constructors call only private, static, or final methods.

14. What are the overriding rules?

Same signature; the return type may be a subtype (covariant); access may widen but not narrow; no new or broader checked exceptions. Static methods are hidden, not overridden: the compile-time type picks them.

15. Two interfaces provide the same default method. What happens?

The class must override it, and it may call one with A.super.m(). A class method always wins over a default, and a more specific interface wins over a less specific one.

16. Abstract class or interface?

An abstract class can hold instance state and constructors; an interface cannot hold instance state but a class can implement many. Interfaces suit capabilities; abstract classes suit shared state and partial implementation.

17. Why prefer composition over inheritance?

A subclass depends on the superclass's self-use (which methods call which). When that changes, the subclass breaks, as in the HashSet add-count example. Wrapping and forwarding calls depends only on the public contract.

18. Static nested class or inner class?

An inner class holds a hidden reference to its outer instance, which can keep a large object alive and leak memory. Use a static nested class unless that reference is needed.

Exceptions, contracts, and immutability

19. Checked or unchecked?

RuntimeException and Error subclasses are unchecked; all other Throwables are checked and must be caught or declared. Unchecked exceptions signal programming errors or failures callers cannot handle; checked ones signal recoverable conditions a caller is expected to handle.

20. What does this method return?

Java
public class FinallyReturn {
    @SuppressWarnings("finally")
    static int f() {
        int x = 1;
        try {
            return x;
        } finally {
            x = 2;
        }
    }

    @SuppressWarnings("finally")
    static int g() {
        try {
            throw new IllegalStateException("lost");
        } finally {
            return 3;
        }
    }

    public static void main(String[] args) {
        System.out.println(f());
        System.out.println(g());
    }
}
Output
1
3

f returns 1: the return value is computed before finally runs. g returns 3: a return in finally overrides the result and discards the exception.

21. Why is try-with-resources better than a manual finally?

It keeps the body's exception as the primary one and attaches close() failures with addSuppressed. A manual finally that throws loses the original. Resources close in reverse order of declaration.

22. Why are exceptions expensive, and how is that avoided?

The main cost is fillInStackTrace, which walks the stack. The Throwable(String, Throwable, boolean, boolean) constructor with writableStackTrace = false, or an override of fillInStackTrace, removes it. HotSpot also enables OmitStackTraceInFastThrow by default, so repeated implicit exceptions can lose their traces.

23. How should InterruptedException be handled?

Rethrow it, or restore the flag with Thread.currentThread().interrupt(). Catching it clears the flag, so swallowing it hides a cancellation request.

24. State the equals and hashCode contracts.

equals: reflexive, symmetric, transitive, consistent, and false for null. Equal objects must have equal hash codes. HashMap picks the bucket by hash first, so overriding equals without hashCode stores equal keys twice and misses lookups.

25. instanceof or getClass() in equals?

instanceof accepts subclasses but breaks symmetry when a subclass adds state. getClass() keeps symmetry but rejects subclasses. Making the class final (or a record) removes the question.

26. Spot the bug.

Java
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class MutableKey {
    static final class Point {
        int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
        @Override public boolean equals(Object o) {
            return o instanceof Point p && p.x == x && p.y == y;
        }
        @Override public int hashCode() { return Objects.hash(x, y); }
    }

    public static void main(String[] args) {
        Set<Point> set = new HashSet<>();
        Point p = new Point(1, 2);
        set.add(p);
        p.x = 10;
        System.out.println(set.contains(p));
        System.out.println(set.size());
    }
}
Output
false
1

The key changed after insertion, so its hash points to a different bucket and the entry is unreachable by lookup while still counted. Keys must be immutable.

27. Why must compare not use subtraction?

a - b overflows for large values of opposite sign and returns the wrong sign. Use Integer.compare(a, b).

28. What is "consistent with equals"?

compareTo returns 0 exactly when equals is true. BigDecimal is not: 1.0 and 1.00 are equal to a TreeSet and different to a HashSet, so the two sets have different sizes.

29. Follow-up: what does comparing(A).thenComparing(B).reversed() reverse?

The whole chain. To reverse only B, write thenComparing(B, Comparator.reverseOrder()) or put reversed() on the inner comparator.

30. Why are copy constructors preferred to clone?

clone is shallow, bypasses constructors, needs the Cloneable marker, and has an awkward checked exception. Arrays are the exception: array.clone() is the normal copy.

31. What makes a class immutable?

final class, private final fields, no mutators, defensive copies of mutable inputs in the constructor and of mutable outputs in accessors. final fixes the reference, not the object it points to.

32. Collections.unmodifiableList vs List.copyOf?

unmodifiableList is a read-only view that shows later changes to the source. List.copyOf is an independent copy that rejects null, and returns its argument if it is already an unmodifiable List.of/copyOf list.

33. What replaces finalize?

AutoCloseable with try-with-resources for deterministic release, and Cleaner as a safety net. finalize is deprecated for removal (JEP 421). A Cleaner action must not reference the object it cleans, or the object never becomes unreachable.

34. Soft vs weak references?

Soft references are cleared under memory pressure (always before an OutOfMemoryError). Weak references are cleared at the next collection that finds the referent weakly reachable. Both are covered in depth in GC Fundamentals.

Records, sealed types, patterns, enums, and modules

35. What does a record generate, and what can it not do?

Private final fields, a canonical constructor, accessors, and equals, hashCode, toString over all components. A record is final, cannot extend a class, and cannot declare extra instance fields.

36. Spot the bug.

Java
import java.util.ArrayList;
import java.util.List;

public class RecordCopy {
    record Basket(List<String> items) {}
    record SafeBasket(List<String> items) {
        SafeBasket { items = List.copyOf(items); }
    }

    public static void main(String[] args) {
        List<String> src = new ArrayList<>(List.of("apple"));
        Basket b = new Basket(src);
        SafeBasket s = new SafeBasket(src);
        src.add("pear");
        System.out.println(b.items());
        System.out.println(s.items());
    }
}
Output
[apple, pear]
[apple]

A record is only shallowly immutable; Basket shares the caller's list. The compact constructor of SafeBasket reassigns the parameter, and the compiler assigns the field after the body. Array components have a similar problem: record equals compares arrays by reference.

37. What must a permitted subclass of a sealed type be?

final, sealed, or non-sealed. Sealed types plus records give algebraic data types, and a switch over them is exhaustive without default; adding a subtype becomes a compile error at every unhandled switch.

38. What does a pattern switch do with null?

Without case null it throws NullPointerException, even when a default exists. case null, default -> handles it.

39. What is dominance?

A case that matches everything a later case matches makes the later case unreachable, which is a compile error. Guarded cases (when) go before the unguarded case of the same type.

40. Follow-up: an exhaustive switch over a sealed interface runs against a class file with a new subtype. What happens?

It throws MatchException at run time. The compiler checks exhaustiveness only against the types it saw.

41. Why compare enums with ==, and why not persist ordinal()?

Each constant is a singleton per class loader, so == is correct and null-safe. ordinal() changes when constants are inserted or reordered; persist name() or an explicit code.

42. What are EnumSet and EnumMap internally?

EnumSet is a bit vector: one long for up to 64 constants (RegularEnumSet), a long[] above (JumboEnumSet). EnumMap is an array indexed by ordinal. Both iterate in declaration order.

43. Why does reflection not see my annotation?

The default retention is CLASS, which is in the class file but not visible at run time. Frameworks need @Retention(RUNTIME). Repeated annotations sit in a container, so getAnnotation returns null and getAnnotationsByType finds them.

44. exports vs opens?

exports allows access to public members at compile and run time. opens allows deep reflection including private members, which binding frameworks need. JDK internals are strongly encapsulated since Java 16; --add-opens is the remaining escape hatch.

45. Scenario: after a JDK upgrade a library fails with InaccessibleObjectException. What is the fix?

The library reflects into a JDK package that is not opened to it. Upgrade the library first; as a stop-gap add --add-opens java.base/<package>=ALL-UNNAMED (or the library's module) for that one package.

46. getMethods vs getDeclaredMethods? What wraps exceptions in reflective calls?

getMethods returns public members including inherited ones; getDeclaredMethods returns all members declared in the class itself. Method.invoke wraps a thrown exception in InvocationTargetException. Proxy implements only interfaces.

Generics

47. Why is List<Integer> not a List<Number>?

Generics are invariant. If it were allowed, a Double could be added through the List<Number> reference. Arrays are covariant and catch the same mistake at run time with ArrayStoreException.

48. Explain PECS with a signature.

Producer extends, consumer super: a parameter the method reads from is ? extends T, a parameter it writes to is ? super T.

Java
import java.util.ArrayList;
import java.util.List;

public class Pecs {
    static <T> void copy(List<? super T> dst, List<? extends T> src) {
        for (T t : src) dst.add(t);
    }

    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        copy(numbers, List.of(1, 2));
        copy(numbers, List.of(2.5));
        System.out.println(numbers);
    }
}
Output
[1, 2, 2.5]

Nothing but null can be added to a List<? extends T>, and reads from a List<? super T> return Object.

49. Why <T extends Comparable<? super T>>?

It accepts a subclass whose compareTo is inherited from a parent: if Child extends Parent and Parent implements Comparable<Parent>, then Child is Comparable<Parent>, not Comparable<Child>.

50. What is type erasure, and what survives it?

The compiler replaces each type parameter with its leftmost bound (or Object) and inserts casts at use sites; one Class object serves all parameterizations. Declared generic signatures (field, method, superclass) stay in the Signature attribute and are readable by reflection; the type argument of an individual object is not.

51. What is a bridge method?

A synthetic method the compiler adds so an override still overrides after erasure, for example compareTo(Object) that casts and calls compareTo(Money). Its cast is where a raw call with the wrong type fails.

52. Why do new T[] and x instanceof List<String> not compile?

Both need the type argument at run time, and it is erased. Workarounds are a Class<T> token (Array.newInstance) or a super type token for full generic types.

53. Spot the bug.

Java
import java.util.ArrayList;
import java.util.List;

public class HeapPollution {
    @SuppressWarnings({"rawtypes", "unchecked"})
    static void addRaw(List list) {
        list.add(42);
    }

    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        addRaw(names);
        System.out.println("size " + names.size());
        try {
            String s = names.get(0);
            System.out.println(s);
        } catch (ClassCastException e) {
            System.out.println("ClassCastException at the read, not at the add");
        }
    }
}
Output
size 1
ClassCastException at the read, not at the add

The raw type disables checks, the list holds an Integer (heap pollution), and the failure appears later at the inserted cast, far from the cause.

54. What does @SafeVarargs promise?

That the method does not write to the generic varargs array or expose it. Generic varargs create a non-reifiable array, so the compiler warns about possible heap pollution.

Lambdas and Optional

55. What is a functional interface?

An interface with exactly one abstract method; default, static, and methods from Object do not count. @FunctionalInterface makes it a compile check.

56. Name the four method reference kinds.

Static (Integer::parseInt), bound instance (list::add), unbound instance (String::length, the first argument is the receiver), and constructor (ArrayList::new).

57. Why must captured locals be effectively final?

The lambda copies the value when it is created. Allowing later writes would make the copy and the variable diverge, and the lambda may run on another thread.

58. What does this mean in a lambda?

The enclosing instance. In an anonymous class, this is the anonymous object.

59. Do lambdas allocate?

They compile to a private method plus an invokedynamic call site. On HotSpot a non-capturing lambda is reused; a capturing lambda allocates a new object each time it is evaluated. Object identity of lambdas is not specified.

60. andThen vs compose?

f.andThen(g) is g(f(x)); f.compose(g) is f(g(x)).

61. Spot the bug.

Java
import java.util.Optional;

public class OrElseCost {
    static String loadDefault() {
        System.out.println("loading default");
        return "default";
    }

    public static void main(String[] args) {
        Optional<String> cached = Optional.of("cached");
        System.out.println(cached.orElse(loadDefault()));
        System.out.println(cached.orElseGet(OrElseCost::loadDefault));
    }
}
Output
loading default
cached
cached

orElse evaluates its argument even when a value is present. Use orElseGet for anything with a cost or a side effect.

62. Where should Optional not be used?

Fields, parameters, collection elements, and as a wrapper around a collection (return an empty collection). It is meant for return types. OptionalInt and friends avoid boxing.

63. map vs flatMap on Optional?

map wraps the function result (a null result gives empty); flatMap expects the function to return an Optional and does not wrap it again.

64. Why use static factories and builders?

Static factories have names, can cache instances, and can return subtypes. Builders take required values in their constructor, validate in build(), and copy mutable inputs. Both keep constructors private so the implementation can change.

Collections

65. How does ArrayList grow?

The default-constructed list allocates 10 slots on the first add, then grows by half its capacity. Adds are amortized O(1); inserting in the middle is O(n) because elements shift. ensureCapacity avoids repeated grows; trimToSize releases slack.

66. Why is LinkedList rarely right?

get(i) is O(n), each element is a separate node object, and traversal chases pointers through memory. ArrayDeque (a circular array) is the usual stack and queue. Stack is a legacy synchronized class.

67. Spot the bug.

Java
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;

public class RemoveWhileIterating {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4));
        try {
            for (Integer x : list) {
                if (x % 2 == 0) list.remove(x);
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("ConcurrentModificationException");
        }
        List<Integer> fixed = new ArrayList<>(List.of(1, 2, 3, 4));
        fixed.removeIf(x -> x % 2 == 0);
        System.out.println(fixed);
    }
}
Output
ConcurrentModificationException
[1, 3]

list.remove increments modCount, and the iterator's next check throws. Use removeIf or Iterator.remove. Fail-fast detection is best effort for single-thread misuse, not a concurrency guarantee.

68. Follow-up: what does list.remove(1) do on a List<Integer>?

It removes the element at index 1. remove(Integer.valueOf(1)) removes the value.

69. List.of, Arrays.asList, unmodifiableList, List.copyOf?

Mutable Nulls Reflects source
List.of no rejected no
Arrays.asList set only, fixed size allowed writes through to the array
Collections.unmodifiableList no allowed yes, a view
List.copyOf no rejected no, a copy

70. What did sequenced collections add in Java 21?

SequencedCollection with getFirst, getLast, addFirst, addLast, removeFirst, removeLast, and reversed, implemented by lists, deques, LinkedHashSet, and sorted sets. SequencedMap adds firstEntry, pollLastEntry, and reversed. Immutable lists throw UnsupportedOperationException from addFirst.

71. Walk through HashMap.put.

Compute hash = h ^ (h >>> 16) to spread high bits into low bits, take index (n - 1) & hash with n a power of two, then search the bucket with equals. If the size passes capacity * 0.75, the table doubles. The default capacity 16 is allocated lazily on the first put.

72. When does a bucket become a tree?

When a put appends to a chain of 8 and the table has at least 64 slots (MIN_TREEIFY_CAPACITY); below 64 the table resizes instead. A tree reverts to a list at 6. Tree bins bound lookup at O(log n), which defends against collision attacks.

73. What happens to entries during a resize?

Each entry stays at index i or moves to i + oldCap, decided by hash & oldCap. No rehash of hashCode is needed. Iteration order is not specified and changes after a resize.

74. Follow-up: new HashMap<>(1000) vs HashMap.newHashMap(1000)?

The constructor argument is a capacity hint; with load factor 0.75 a map sized 1024 resizes before it holds 1000 entries. HashMap.newHashMap(n) (Java 19) sizes the table so n entries fit without a resize.

75. Coding: an LRU cache in a few lines.

Java
import java.util.LinkedHashMap;
import java.util.Map;

public class Lru {
    static <K, V> Map<K, V> lru(int max) {
        return new LinkedHashMap<>(16, 0.75f, true) {
            @Override protected boolean removeEldestEntry(Map.Entry<K, V> e) {
                return size() > max;
            }
        };
    }

    public static void main(String[] args) {
        Map<String, Integer> cache = lru(2);
        cache.put("a", 1);
        cache.put("b", 2);
        cache.get("a");
        cache.put("c", 3);
        System.out.println(cache.keySet());
    }
}
Output
[a, c]

The true argument switches to access order, so get("a") moves a to the end and b is evicted. It is not thread-safe.

76. IdentityHashMap and WeakHashMap?

IdentityHashMap compares keys with == and uses open addressing. WeakHashMap holds keys weakly and drops entries whose keys are collected; it breaks if values reference their keys.

77. When TreeMap instead of HashMap?

When order or range queries matter: floorKey, ceilingEntry, headMap, subMap run in O(log n) on a red-black tree, and the views are live. HashMap is O(1) on average with no order.

78. Spot the bug: a TreeSet<Trade> ordered by price has fewer elements than were added.

Sorted collections use compare, not equals. Two trades at the same price compare as 0 and the second is dropped. Add a tie-breaker on a unique field: comparing(Trade::price).thenComparing(Trade::id).

79. Coding: top k of n values.

Keep a min-heap of size k; replace the head when a larger value arrives. O(n log k) time, O(k) memory.

Java
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

public class TopK {
    static List<Integer> topK(int[] values, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        for (int v : values) {
            if (heap.size() < k) heap.offer(v);
            else if (v > heap.peek()) { heap.poll(); heap.offer(v); }
        }
        List<Integer> out = new ArrayList<>();
        while (!heap.isEmpty()) out.add(heap.poll());
        return out.reversed();
    }

    public static void main(String[] args) {
        System.out.println(topK(new int[] {5, 1, 9, 3, 7, 8, 2}, 3));
    }
}
Output
[9, 8, 7]

The result comes from repeated poll, because a PriorityQueue iterator is not in priority order.

80. What does boxing cost in collections?

Each Integer outside -128..127 is a 16-byte object, and the list stores a 4-byte reference to it (compressed oops), about 20 bytes per element against 4 for int[]. A HashMap<Integer, Integer> entry costs a node plus up to two boxes, about 70 bytes per entry in the lesson's measurement.

81. Chaining vs open addressing?

Chaining (HashMap) allocates a node per entry in per-bucket lists. Open addressing (fastutil Int2IntOpenHashMap, Agrona Int2IntHashMap) stores keys and values in arrays and probes on collision, so there are no per-entry objects. Removal needs back-shifting or tombstones, and a "missing" value (defaultReturnValue() in fastutil, a constructor argument in Agrona) replaces null.

Streams

82. When does a stream pipeline run?

Only when the terminal operation starts. Elements flow one at a time through all stages; stateful operations such as sorted and distinct buffer. Short-circuiting operations (findFirst, anyMatch, limit, takeWhile) make infinite streams usable.

83. Spot the bug.

Java
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ToMapDup {
    record Trade(String symbol, int qty) {}

    public static void main(String[] args) {
        List<Trade> trades = List.of(new Trade("A", 1), new Trade("B", 2), new Trade("A", 3));
        try {
            trades.stream().collect(Collectors.toMap(Trade::symbol, Trade::qty));
        } catch (IllegalStateException e) {
            System.out.println("IllegalStateException: duplicate key");
        }
        Map<String, Integer> totals = trades.stream()
                .collect(Collectors.toMap(Trade::symbol, Trade::qty, Integer::sum));
        System.out.println(new java.util.TreeMap<>(totals));
    }
}
Output
IllegalStateException: duplicate key
{A=4, B=2}

toMap throws on a duplicate key unless it gets a merge function.

84. Stream.toList() vs Collectors.toList()?

Stream.toList() (Java 16) returns an unmodifiable list that allows null. Collectors.toList() makes no promise about mutability; today it returns an ArrayList.

85. What does reduce require?

An associative function and a true identity (identity op x == x). Otherwise the result changes when a parallel stream splits differently. collect is the mutable reduction for building containers.

86. Coding: count words by first letter, sorted by letter.

Java
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class GroupCount {
    public static void main(String[] args) {
        List<String> words = List.of("apple", "avocado", "banana", "cherry", "blueberry");
        Map<Character, Long> counts = words.stream()
                .collect(Collectors.groupingBy(w -> w.charAt(0), TreeMap::new, Collectors.counting()));
        System.out.println(counts);
    }
}
Output
{a=2, b=2, c=1}

groupingBy defaults to a HashMap; the map factory argument gives a sorted result.

87. Why are side effects in stream lambdas a problem, and can a stream be reused?

Writes to shared state from forEach or map race under parallel execution and depend on evaluation order; collect results instead. A stream is one-shot and throws IllegalStateException on reuse; use a Supplier<Stream<T>>.

88. What is a gatherer?

A user-defined intermediate operation (Stream.gather, final in Java 24) made of an initializer, an integrator, an optional combiner, and an optional finisher. The integrator returns false to stop the stream. Built-ins: windowFixed, windowSliding, fold (one result at the end), scan (running result per element), and mapConcurrent (bounded concurrency on virtual threads, encounter order preserved).

89. When do parallel streams help?

Large, well-splitting sources (arrays, ArrayList, IntStream.range) with CPU-heavy work per element. They hurt for small inputs and for linked or iterate-based sources. All parallel streams share ForkJoinPool.commonPool(), so a blocking task there slows every parallel stream in the JVM.

90. Follow-up: which terminal operations keep order in parallel?

collect and findFirst keep encounter order; forEach and findAny do not.

91. Scenario: a profiler shows a stream on the order-processing hot path. What changes?

Stream<Integer> boxes, and each pipeline creates stage objects and makes interface calls. Replace it with a loop over a primitive array, or at least a primitive stream, and measure allocation per operation before and after.

Testing

92. What are the parts of JUnit 5?

Platform (launcher and engine API), Jupiter (the programming model and engine), and Vintage (runs JUnit 4 tests). Jupiter creates a new test-class instance per test method by default; @TestInstance(PER_CLASS) shares one, and then @BeforeAll need not be static.

93. assertTimeout vs assertTimeoutPreemptively?

assertTimeout runs the code in the test thread and fails after it finishes late. assertTimeoutPreemptively runs it in another thread and abandons it, which breaks ThreadLocal state such as a transaction bound to the test thread.

94. What does assertAll add, and what do assumptions do?

assertAll runs every check and reports all failures. A failed assumption aborts the test and marks it skipped, not failed.

95. Stub, mock, spy, or fake?

A stub returns canned answers and the test checks state. A mock records calls and the test verifies interactions. A spy wraps a real object. A fake is a working lightweight implementation (an in-memory repository). Fakes survive refactoring; mocks tie tests to calls.

96. Spot the bug.

Java
when(repo.find(anyString(), 42)).thenReturn(order);

Mixing matchers and raw values throws InvalidUseOfMatchersException; when any argument is a matcher, all must be: find(anyString(), eq(42)). (The block is not run here because it needs a mock set up by the surrounding test.)

97. Why does when(spy.method()) call the real method?

when(...) evaluates the call first. Spies and void methods use doReturn(x).when(spy).method() or doThrow.

98. What do strict stubs do, and can Mockito mock final classes?

With MockitoExtension, an unused stubbing fails the test, and a call with mismatching arguments throws PotentialStubbingProblem. Mockito 5 uses the inline mock maker by default, so final classes and records can be mocked.

99. How is time tested?

Inject a Clock instead of calling Instant.now(). Tests pass Clock.fixed or a mutable test clock; with a manual scheduler, retries and timeouts run deterministically on the test thread.

100. Scenario: a stress test for a counter passes 1,000 times. Is the counter thread-safe?

Not proven. A passing stress test only shows the race did not happen on that machine and run. Start threads together with a CountDownLatch(1) gate, force the bad interleaving with test hooks to reproduce a known race every time, and use jcstress, which runs tiny concurrent tests many times and classifies outcomes as acceptable, interesting, or forbidden.

101. How does Testcontainers manage container lifecycle?

A static @Container field lives for the test class, an instance field for each test. Read the mapped port or JDBC URL from the container at run time because it is random. withReuse(true) keeps a container across runs only with the opt-in property.

Interview points

  • Answers on wrappers, strings, and overflow rest on identity vs equality and silent wrap-around.
  • equals/hashCode, compareTo consistency, and immutable keys explain most collection bugs.
  • Records, sealed types, and pattern switch give compiler-checked data modelling; null and separate compilation are the edge cases.
  • Erasure explains bridge methods, heap pollution, and the illegal generic forms.
  • HashMap numbers: spread h ^ (h >>> 16), load factor 0.75, treeify at 8 with capacity 64, untreeify at 6.
  • Streams are lazy, one-shot, and need associative reductions; parallel streams share the common pool.
  • Tests prefer fakes and injected clocks; stress tests do not prove thread safety.

Summary

  • 101 questions cover every lesson in sections 1 to 6.
  • Runnable answers show integer caching, unboxing null, finally with return, mutable keys, heap pollution, orElse cost, fail-fast iteration, an LRU cache, top-k, and toMap duplicates.
  • Concurrency, the memory model, and lock-free code are in Concurrency and JMM questions; JVM, GC, and performance in JVM, JIT, GC, and Performance questions.