Since Java 21, a case can test a type and bind it: case Circle c ->. Add a guard with when, take records apart with record patterns, and handle null with case null. Over a sealed type the compiler checks every subtype is covered, so no default is needed.
Fail-fast iterators (ArrayList, HashMap) track a modification counter and throw ConcurrentModificationException if the collection changes behind their back. "Fail-safe" ones (CopyOnWriteArrayList, ConcurrentHashMap) walk a snapshot or a live view and never throw.
ExampleJava
List<String> tasks = new ArrayList<>(List.of("draft", "spam", "review", "spam"));// Throws ConcurrentModificationExceptionfor (String t : tasks) { if (t.equals("spam")) tasks.remove(t);}// Fine: the iterator removes and stays in sync…
HashSet: O(1) average, no order, uses hashCode/equals. LinkedHashSet: same speed plus insertion order. TreeSet: O(log n), kept sorted by compareTo or a Comparator, and that comparison (not equals) decides what counts as a duplicate.
ExampleJava
List<String> names = List.of("mira", "Zed", "ann", "mira", "Bo");new HashSet<>(names); // e.g. [ann, Bo, mira, Zed] (order not guaranteed)new LinkedHashSet<>(names); // [mira, Zed, ann, Bo]new TreeSet<>(names); // [Bo, Zed, ann, mira] (uppercase sorts first)// Comparator decides duplicates in a TreeSetSet<String> tags = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);…
Iterator walks any collection forward and can remove. ListIterator is List-only: it moves both ways, knows indexes, and can set or add. Spliterator (Java 8) can split itself in two, which is what lets streams hand chunks of a collection to parallel workers.
ExampleJava
List<String> words = new ArrayList<>(List.of("tea", "cofee", "juice"));// ListIterator: fix a typo in place and insert after itListIterator<String> li = words.listIterator();while (li.hasNext()) { if (li.next().equals("cofee")) { li.set("coffee"); li.add("cocoa"); // inserted after "coffee"…
Producers put() items on a BlockingQueue and consumers take() them. put() waits while a bounded queue is full and take() waits while it's empty, so the queue handles all locking and signalling. Stop consumers with a poison pill or by interrupting them.
ExampleJava
record Order(int id) {}static final Order DONE = new Order(-1); // poison pillpublic static void main(String[] args) throws Exception { BlockingQueue<Order> queue = new ArrayBlockingQueue<>(100); int consumers = 2; Thread producer = Thread.ofPlatform().start(() -> {…
.parallel() splits the source and runs chunks on the common ForkJoinPool. It pays off for large, easily split data (arrays, ArrayList, ranges) with CPU-heavy, independent work per element. It hurts for small inputs, blocking I/O, LinkedList sources, ordering and shared state.
ExampleJava
static boolean isPrime(int n) { if (n < 2) return false; for (int d = 2; (long) d * d <= n; d++) if (n % d == 0) return false; return true;}// Good fit: big range, CPU-only work, cheap mergelong primes = IntStream.range(0, 5_000_000).parallel().filter(Main::isPrime).count();…
A sealed class or interface lists exactly which types may extend it. You keep a public type but a closed set of subtypes, so the compiler can check a switch covers them all. Every subtype states whether the hierarchy stays closed or opens again (non-sealed). Java 17+.
ThreadLocal is a mutable per-thread slot that lives until you remove it. ScopedValue binds an immutable value for one block of code and unbinds it when the block ends, and is cheap with many virtual threads. Preview in Java 21, final in Java 25.
ExampleJava
// ThreadLocal: you must clean up yourselfstatic final ThreadLocal<String> TENANT = new ThreadLocal<>();void handleOld(Request req) { TENANT.set(req.tenant()); try { billing.charge(req); // deep code calls TENANT.get() } finally {…
== on objects asks "same instance?". equals() asks "same value?", and is == unless a class overrides it. The contract: objects that are equal must return the same hashCode(). Break it and HashMap and HashSet stop finding your objects.
ExampleJava
final class Money { private final long cents; private final String currency; Money(long cents, String currency) { this.cents = cents; this.currency = currency; } @Override public boolean equals(Object o) { if (this == o) return true;…
ThreadLocal — what it is and the classic memory leak
Hard
A ThreadLocal is a slot where every thread reads and writes a separate value, stored in a map on the Thread object itself. Pool threads never die, so without remove() the value outlives the request: memory leaks and the next task sees stale data.
ExampleJava
final class TenantHolder { private static final ThreadLocal<String> TENANT_ID = new ThreadLocal<>(); static void bind(String tenantId) { TENANT_ID.set(tenantId); } static String tenant() { return TENANT_ID.get(); } static void unbind() { TENANT_ID.remove(); }}// Runs on a pooled worker thread for each incoming job…
ForkJoinPool runs divide-and-conquer tasks. Each worker has its own deque: it pushes and pops its subtasks at one end, and idle workers steal the oldest, biggest tasks from the other end. Little contention, busy cores. Parallel streams use its common pool.
ExampleJava
class SumTask extends RecursiveTask<Long> { private static final int CUTOFF = 10_000; private final long[] data; private final int from, to; SumTask(long[] data, int from, int to) { this.data = data; this.from = from; this.to = to; } @Override protected Long compute() {…
Java 15 made text blocks standard: a multi-line string between """ delimiters. The compiler strips the shared indentation and trailing spaces and turns line endings into \n. No escaped quotes or + chains. The result is an ordinary String.
ExampleJava
// Before: escapes and concatenationString oldJson = "{\n" + " \"sku\": \"%s\",\n" + " \"qty\": %d\n" + "}\n";// After: what you see is what you getString json = """…
put() hashes the key, folds the high bits into the low bits, and uses (n - 1) & hash to pick a bucket. Empty bucket: store it. Otherwise match by hash + equals() and replace, or append. Crowded buckets turn into trees; past 75% full the table doubles.
ExampleJava
Map<String, Integer> stock = new HashMap<>();stock.put("apple", 10); // null: new keyInteger before = stock.put("apple", 7); // 10: value replacedstock.put(null, 0); // allowed, lives in bucket 0
String immutability — why is String immutable in Java?
Easy
A String can't change after it's built, so the JVM can share one copy of a literal through the string pool, cache its hash code, and pass it between threads with no locks. A checked file name or class name also can't be swapped out after the check.
ExampleJava
String city = "lisbon";city.toUpperCase(); // result thrown away; city is still "lisbon"String shout = city.toUpperCase(); // "LISBON": a new objectString x = "lisbon";boolean shared = (city == x); // true: both point to the pooled literal// Many appends: one mutable buffer, one final String…
ArrayList keeps elements in one array: O(1) get(i), fast appends, compact and cache-friendly. LinkedList is a chain of nodes: cheap at the ends, but get(i) walks the chain and every node costs extra memory. Use ArrayList by default and ArrayDeque for queues.
ExampleJava
List<Integer> linked = new LinkedList<>();List<Integer> array = new ArrayList<>();for (int i = 0; i < 100_000; i++) { linked.add(i); array.add(i); }// O(n²) on LinkedList: every get(i) walks the chainlong slow = 0;for (int i = 0; i < linked.size(); i++) slow += linked.get(i);…
volatile makes one variable's writes visible to other threads but doesn't make count++ atomic. Atomic classes do single-variable read-modify-write with CAS, no lock. synchronized gives mutual exclusion plus visibility, for rules that span several fields.
ExampleJava
class Downloader { private volatile boolean cancelled; // one writer, many readers private final AtomicLong bytes = new AtomicLong(); private final List<String> failed = new ArrayList<>(); private int failures; void cancel() { cancelled = true; } // seen by worker threads promptly…
Checked vs unchecked exceptions — when to use which?
Medium
Checked exceptions (Exception but not RuntimeException) must be caught or declared, so the compiler forces callers to think about them. Use them for failures a caller can act on. Unchecked ones (RuntimeException, Error) signal bugs or failures nobody nearby can fix.
ExampleJava
// Checked: the caller can decide to retry or pick another filestatic String loadTemplate(Path path) throws IOException { return Files.readString(path);}// Unchecked: a caller passing a negative quantity has a bugstatic void reserve(String sku, int qty) { if (qty <= 0) throw new IllegalArgumentException("qty must be positive, got " + qty);…
The JVM runs bytecode: it loads classes, verifies them, interprets or JIT-compiles them, and collects garbage. The JRE is a JVM plus the standard libraries, enough to run apps. The JDK adds tools to build them: javac, jar, jshell, jlink and more.
ExampleJava
// Greeter.javapublic class Greeter { public static void main(String[] args) { System.out.println("Running on " + System.getProperty("java.vm.name") + " " + Runtime.version()); }}
final vs finally vs finalize — three different things
Easy
final is a modifier: a variable assigned once, a method that can't be overridden, a class that can't be extended. finally is the block that runs after try/catch however it exits. finalize() was a GC callback, deprecated for removal since Java 18.
ExampleJava
final class Invoice { // can't be subclassed private final List<String> lines = new ArrayList<>(); void add(String l) { lines.add(l); } // fine: the list is mutable, the reference isn't}static int attempts() { int n = 1; try {…
Singleton pattern — 4 ways to implement it, which is thread-safe?
Medium
Eager static field: simple, thread-safe. Lazy null check: broken under threads. Double-checked locking: safe only with volatile. Holder class or enum: lazy or simple, thread-safe with no locking. The enum version also survives serialization and reflection.
ExampleJava
// 3. Double-checked locking: volatile is requiredfinal class RateLimiter { private static volatile RateLimiter instance; private RateLimiter() {} static RateLimiter get() { RateLimiter local = instance; // one volatile read on the fast path if (local == null) { synchronized (RateLimiter.class) {…