Understand the states a Java thread passes through (NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED) and the transitions between them. SDE 2 interviews often test this as a lead-in to deeper concurrency questions.
start() is called, not run()synchronized blockwait() vs wait(timeout))Thread.sleep() does not release the monitor lockjstack to inspect thread states in productionThe volatile keyword guarantees visibility across threads and establishes a happens-before relationship. Critical for understanding the Java Memory Model (JMM) and why some race conditions exist despite seemingly safe code.
volatile prevents instruction reordering and ensures reads see the latest writevolatile is NOT atomic for compound operations like i++AtomicInteger / AtomicReference when you need atomicityThe foundational mutual-exclusion mechanism in Java. Know the difference between method-level and block-level synchronization, and how the JVM implements it via monitor locks.
this vs a dedicated lock object — the latter is saferClass objectExplicit lock APIs from java.util.concurrent.locks that offer more control than synchronized — fairness policies, interruptible locking, and try-lock with timeout.
ReentrantLock allows the same thread to acquire the lock multiple times (reentrant)tryLock(timeout) prevents indefinite blocking — key for deadlock avoidanceReadWriteLock: multiple readers OR one writer — major throughput gain for read-heavy workloadslock() / unlock() in a try-finally blockProvides thread-confined variables — each thread gets its own independent copy. Commonly used for user context, database connections, and date formatters in web applications.
get() / set()remove() in a finally block, especially in thread poolsInheritableThreadLocal passes values to child threadsThreadLocal for RequestContextHolder and transaction contextsThe standard way to manage thread pools in Java. Interviewers expect SDE 2 candidates to know pool types, rejection policies, and when to use which pool.
FixedThreadPool: bounded pool, tasks queue up if all threads busyCachedThreadPool: unbounded, creates threads on demand — risk of OOM under loadScheduledThreadPool: for periodic / delayed tasksshutdown() + awaitTermination() for graceful cleanupA specialized pool for divide-and-conquer parallelism. Uses work-stealing to keep all threads busy. Powers parallelStream() behind the scenes.
RecursiveTask<V> (returns result) or RecursiveAction (void)ForkJoinPool.commonPool() is shared across the JVM — be careful in web serversThe modern way to compose asynchronous operations in Java. Replaces callback hell with fluent, chainable stages. A frequent SDE 2 interview topic.
thenApply (sync transform), thenCompose (flatMap), thenCombine (merge two futures)supplyAsync runs on ForkJoinPool by default — pass custom executor for IO-bound workexceptionally and handle for error recoveryallOf / anyOf to fan-out and aggregateDeadlock happens when two or more threads wait forever for locks held by each other. Interviewers love asking candidates to identify, prevent, or resolve deadlocks from code snippets.
tryLock(timeout) breaks the hold-and-wait conditionjstack — it reports deadlocked threads automaticallyThreadMXBean.findDeadlockedThreads() for programmatic detectionHow HashMap works under the hood is arguably the most asked Java interview question. Know the internal array + linked-list/tree structure, load factor, and treeification threshold.
hashCode() and equals() consistentlyConcurrentHashMap in concurrent contextsA HashMap that also maintains a doubly-linked list of entries to preserve insertion order (or access order). The basis for LRU cache implementations in interviews.
accessOrder=true for LRU behaviorremoveEldestEntry() to build a bounded LRU cacheThe go-to thread-safe map. Java 8 replaced the old segment-lock design with per-bucket CAS + synchronized nodes, dramatically improving concurrency.
computeIfAbsent is atomic — a clean alternative to double-check idiomsA red-black tree based NavigableMap that keeps keys sorted. Know when to choose TreeMap over HashMap and how comparators affect behavior.
floorKey, ceilingKey, subMap — powerful range queriesequals() is important — inconsistency breaks Map contractBacked by a min-heap (binary heap array). Essential for top-K, merge-K-sorted, and scheduling problems in coding interviews.
Collections.reverseOrder() or custom comparator for max-heapoffer/poll are O(log n), peek is O(1)remove(Object) is O(n) — avoid if performance mattersPriorityBlockingQueue for concurrent accessArrayDeque is faster than LinkedList for both stack and queue use cases. Know why, and when LinkedList still makes sense.
The JVM heap is divided into generational regions. Understanding this layout is fundamental to tuning GC and diagnosing memory issues in production.
Different collectors optimize for different goals — throughput, latency, or footprint. SDE 2 interviews test whether you can pick the right one for a given scenario.
Knowing the key JVM flags lets you tune garbage collection behavior for your application's workload. Expect "how would you tune GC for X" questions.
-XX:MaxGCPauseMillis=200 — G1 pause target-XX:NewRatio=2 — Old/Young ratio (default: Old is 2x Young)-XX:SurvivorRatio=8 — Eden/Survivor ratio-XX:+UseStringDeduplication — reduces heap for string-heavy apps (G1 only)-XX:MetaspaceSize / -XX:MaxMetaspaceSize — control class metadata growthJava has GC, but memory leaks still happen — typically through unintentional strong references. Know the reference types and common leak patterns.
WeakReference: collected when no strong refs remain — used in WeakHashMapSoftReference: collected only under memory pressure — good for cachesPhantomReference: for cleanup actions after object is collected (replaces finalize)finalize() is deprecated (Java 9+) and should never be relied on. Know why it is problematic and what modern alternatives exist.
finalize() runs non-deterministically, may never run at allCleaner API (Java 9+) or try-with-resources for AutoCloseableStreams provide a declarative pipeline for data processing. Understand lazy evaluation — intermediate operations build the pipeline; terminal operations trigger execution.
filter, map, flatMap, sorted, distinct — all lazycollect, forEach, reduce, count — triggers executionfindFirst, anyMatch, limit — stop earlyOptional is a container for nullable values that makes intent explicit. Know the anti-patterns — Optional.get() without check, using Optional as method parameter.
orElse, orElseGet, orElseThrow — never raw get()orElse always evaluates the default; orElseGet is lazy — matters for expensive defaultsmap / flatMap for chaining transformations on the wrapped valueShorthand for lambdas that simply call an existing method. Clean, readable, and commonly used with streams and functional interfaces.
Class::staticMethod), instance (obj::method), arbitrary object (Class::instanceMethod), constructor (Class::new)x -> x.toString() becomes Object::toStringJava 8 added default and static methods in interfaces, enabling API evolution without breaking implementations. Know the diamond problem implications.
default provides a body that implementing classes inherit (or override)static methods in interfaces are utility methods — not inherited by implementing classesprivate interface methods to share code between defaultsRecords (Java 16) are immutable data carriers that auto-generate equals, hashCode, toString. Sealed classes (Java 17) restrict which classes can extend them — powerful for domain modeling.
record Point(int x, int y) {} — concise, immutable, transparentsealed class Shape permits Circle, Square {}final, sealed, or non-sealedJava 16 eliminates the redundant cast after instanceof. A small but impactful readability improvement that interviewers may mention as a modern Java signal.
if (obj instanceof String) { String s = (String) obj; ... }if (obj instanceof String s) { ... } — binding variable s is in scopeJava 15 text blocks use triple-quote """ syntax for multi-line strings. Eliminates escape-character noise for JSON, SQL, and HTML literals.
"""\s (explicit space) and \ (line continuation) escape sequencesString.formatted() for template-like usageJava 10 introduced local variable type inference with var. Reduces boilerplate but has guardrails — cannot be used for fields, parameters, or return types.
var list = new ArrayList<String>();The essential JDK command-line tools for diagnosing live JVM processes. Interviewers test whether you can actually troubleshoot production issues, not just write code.
jstack <pid>: prints thread dump — find deadlocks, blocked threads, CPU hotspotsjmap -heap <pid>: heap summary. jmap -dump:format=b,file=heap.hprof <pid>: full heap dumpjstat -gcutil <pid> 1000: GC stats every 1s — monitor GC frequency and pause times-l flag with jstack for extended info including lock ownershipGUI-based profiling tools for CPU profiling, memory analysis, and thread monitoring. Be familiar with their capabilities even if you primarily use command-line tools.
A heap dump captures every live object on the heap at a point in time. Analyzing it lets you find memory leaks, oversized caches, and unexpected object retention.
jmap, JMX, or -XX:+HeapDumpOnOutOfMemoryErrorA thread dump shows what every thread is doing at a given instant. The primary tool for diagnosing deadlocks, thread starvation, and high-latency requests.
jstack, kill -3 <pid>, or JMX ThreadMXBeanGC logs record every collection event with timing and memory details. Parsing them is how you validate tuning changes and diagnose pause-time issues.
-Xlog:gc*:file=gc.log:time,uptime,level,tags (Java 9+ unified logging)-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.logThe most fundamental JVM flags. -Xms sets initial heap size, -Xmx sets maximum heap size. Setting them equal avoids costly heap resizing at runtime.
-Xms512m -Xmx2g: start at 512 MB, grow up to 2 GB-Xms == -Xmx in production to avoid resize pauses-XX:MaxRAMPercentage=75.0 for DockerEnables the G1 (Garbage First) collector. Default since Java 9. Know when G1 is the right choice and how its region-based model differs from older collectors.
-XX:MaxGCPauseMillis=200 (default)Enables detailed GC logging — essential for understanding what the collector is doing and how to tune it. The first flag you add when diagnosing GC issues.
-XX:+PrintGCDetails -XX:+PrintGCDateStamps-Xlog:gc*=info:file=gc.log:time-XX:+PrintGCApplicationStoppedTime for full picture-XX:+HeapDumpOnOutOfMemoryError automatically captures a heap dump when the JVM runs out of memory. A must-have flag for every production JVM.
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/logs/heap.hprof-XX:OnOutOfMemoryError="kill -9 %p" to force restart after dump