Cheat SheetsInterview Q&ACore Java

Core Java — Cheat Sheet

Interview Q&A · 100 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Core Java
Interview Q&A100 topicsQuick revision reference
1

What is the difference between == and .equals() in Java?

== compares references (memory addresses) for objects — it checks if both variables point to the exact same object. .equals() compares the logical content of objects. For primitive types (int, char, etc.), == compares values directly. For String and wrapper classes, .equals() is overridden to compare content, so "hello" == "hello" may be true for interned strings but is unreliable — always use .equals() for object comparison.

2

What is the difference between abstract class and interface?

An abstract class can have state (instance variables), constructors, and both abstract and concrete methods. A class can extend only one abstract class. An interface defines a contract — all methods are implicitly public and abstract (before Java 8). Since Java 8, interfaces can have default and static methods. A class can implement multiple interfaces. Use an abstract class when you want to share code among related classes. Use an interface when you want to define a capability that unrelated classes can implement.

3

Explain the SOLID principles.

S — Single Responsibility: A class should have only one reason to change. O — Open/Closed: Open for extension, closed for modification (use inheritance/interfaces). L — Liskov Substitution: Subtypes must be substitutable for their base types without breaking correctness. I — Interface Segregation: Clients should not be forced to depend on methods they don't use. Prefer small, specific interfaces. D — Dependency Inversion: Depend on abstractions, not concretions. High-level modules should not depend on low-level modules. Following SOLID leads to loosely coupled, testable, maintainable code.

4

How does HashMap work internally?

HashMap uses an array of Node buckets. When you put(key, value), it computes key.hashCode(), applies a secondary hash, then uses (n-1) & hash to find the bucket index. If the bucket is empty, the node is placed directly. If there's a collision (two keys hash to the same bucket), nodes are chained as a linked list. When a bucket's list size exceeds 8 (and total capacity > 64), it converts to a TreeMap (Red-Black Tree) for O(log n) lookup. Default initial capacity is 16 with a load factor of 0.75. When 75% full, it resizes to 2× capacity and rehashes all entries.

5

What is the difference between HashMap and ConcurrentHashMap?

HashMap is not thread-safe — concurrent reads/writes can cause data corruption, infinite loops (Java 6), or lost updates. ConcurrentHashMap uses segment-level locking (Java 7) or CAS + synchronized on individual buckets (Java 8+). Reads are lock-free. Writes lock only the affected bucket, allowing high concurrency. ConcurrentHashMap does not allow null keys or values (to avoid ambiguity in concurrent get()). Use it in multi-threaded environments; HashMap in single-threaded.

6

Explain Java Memory Model: Heap, Stack, and Metaspace.

Stack: Each thread has its own stack. Stores method frames, local variables, and references. Memory is automatically freed when the method returns. Fast but limited in size. Heap: Shared across all threads. Stores all objects and class instances. Divided into Young Generation (Eden + Survivor spaces) and Old Generation. Managed by Garbage Collector. Metaspace (replaced PermGen in Java 8): Stores class metadata, method bytecode, and constant pool. Grows dynamically in native memory — no more OutOfMemoryError: PermGen space. String Pool is in the Heap since Java 7.

7

What is the volatile keyword in Java?

volatile guarantees visibility: writes to a volatile variable are immediately flushed to main memory, and reads always fetch from main memory instead of CPU cache. Without volatile, threads may see stale cached values. volatile prevents this but does NOT make compound operations atomic (e.g., i++ is still not thread-safe even with volatile). Use volatile for flags (boolean running = true) or single-write-multiple-read scenarios. For compound operations, use AtomicInteger or synchronized.

8

What is the difference between synchronized and ReentrantLock?

synchronized is a built-in keyword — simpler to use, automatically releases the lock on exit or exception, but lacks advanced features. ReentrantLock (java.util.concurrent) offers: • tryLock() — non-blocking attempt with optional timeout • lockInterruptibly() — can be interrupted while waiting • Fair locking — FIFO ordering of waiting threads • Multiple Condition objects (await/signal) vs single wait/notify • Explicit lock/unlock (must be in try-finally) Both are reentrant — a thread can acquire the same lock multiple times. Prefer synchronized for simplicity; use ReentrantLock when you need the extra control.

9

What is a deadlock and how do you prevent it?

A deadlock occurs when two or more threads are blocked forever, each waiting for a resource held by the other. Four conditions must hold (Coffman conditions): Mutual exclusion, Hold-and-Wait, No preemption, Circular wait. Prevention strategies: • Lock ordering: Always acquire locks in a consistent global order • Lock timeout: Use ReentrantLock.tryLock(timeout) and release if timeout • Lock-free algorithms: Use Atomic classes or ConcurrentHashMap • Avoid nested locking: Minimize synchronized blocks • Deadlock detection: Use jstack or VisualVM to detect deadlocks in production

10

What are functional interfaces and lambda expressions in Java 8?

A functional interface has exactly one abstract method (SAM — Single Abstract Method). @FunctionalInterface annotation enforces this at compile time. Key built-in functional interfaces: Runnable (no args, no return), Supplier<T> (no args, returns T), Consumer<T> (takes T, no return), Function<T,R> (T→R), Predicate<T> (T→boolean), BiFunction<T,U,R>. Lambda expressions provide a concise way to implement functional interfaces: (args) -> expression or (args) -> { body }. Example: list.stream().filter(s -> s.startsWith("A")).map(String::toUpperCase).collect(Collectors.toList())

11

What is Optional and when should you use it?

Optional<T> is a container that may or may not hold a non-null value. It was introduced in Java 8 to reduce NullPointerExceptions and make nullable return values explicit. Key methods: of(value) (throws NPE if null), ofNullable(value) (safe), empty(), isPresent(), get(), orElse(default), orElseGet(supplier), orElseThrow(exception), map(), flatMap(), filter(). Best practices: Use as return types (never as method parameters or fields). Chain operations with map/flatMap instead of isPresent() checks. Example: return findUser(id).map(User::getName).orElse("Unknown");

12

Explain the Stream API and intermediate vs terminal operations.

Stream API processes sequences of elements in a functional pipeline. Streams are lazy — intermediate operations are not executed until a terminal operation is invoked. Intermediate operations return a new Stream and are lazy: filter(), map(), flatMap(), sorted(), distinct(), limit(), skip(), peek(). Terminal operations trigger computation and return a result: collect(), forEach(), count(), reduce(), findFirst(), findAny(), anyMatch(), allMatch(), toList() (Java 16+). Parallel streams (stream().parallel()) use ForkJoinPool for parallel processing — useful for CPU-intensive stateless operations on large datasets, but add overhead for small collections.

13

What is the difference between checked and unchecked exceptions?

Checked exceptions extend Exception (but not RuntimeException) — the compiler forces you to either catch or declare them with throws. Examples: IOException, SQLException, ClassNotFoundException. Unchecked exceptions extend RuntimeException — no compile-time enforcement. Examples: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException. Error (OutOfMemoryError, StackOverflowError) is a separate hierarchy indicating JVM-level problems — should never be caught. Modern practice (Spring, Hibernate) wraps checked exceptions in unchecked ones to avoid cluttering code with try-catch for unrecoverable errors.

14

What is the difference between String, StringBuilder, and StringBuffer?

String is immutable — every concatenation creates a new object. The JVM interns String literals in the String Pool. Good for constants and keys. StringBuilder is mutable and not thread-safe. Best for building strings in a single thread (loops, concatenation). Much faster than String concatenation in loops. StringBuffer is mutable and thread-safe (synchronized methods). Same API as StringBuilder but with synchronization overhead. Rarely needed in modern code — use StringBuilder + external synchronization if needed. Java compiler converts "a" + "b" + "c" to StringBuilder operations at compile time, but only for single expressions — not across loop iterations.

15

What is garbage collection? How does G1 differ from ZGC?

Garbage collection automatically reclaims memory occupied by unreachable objects. The JVM tracks object reachability from GC roots (stack variables, static fields, JNI references). G1GC (default since Java 9): Divides heap into equal-size regions. Prioritizes regions with most garbage ("Garbage First"). Aims for configurable pause targets (-XX:MaxGCPauseMillis). Good for heaps 4GB–32GB with predictable pauses. ZGC (Java 15+ production-ready): Concurrent, low-latency GC. Uses colored pointers and load barriers. Sub-millisecond pause times regardless of heap size (tested up to 16TB). Trades slightly higher CPU overhead for minimal stop-the-world. Ideal for latency-sensitive services.

16

What is method overloading vs method overriding?

Overloading (compile-time polymorphism): Same method name, different parameter list (type, count, order). Resolved at compile time based on the static type of the reference. Overriding (runtime polymorphism): Subclass provides a specific implementation of a method defined in the parent. Must have same name, same parameters, same or covariant return type. @Override annotation catches errors. The actual method called depends on the runtime type of the object. Key rule: You cannot override static, final, or private methods. Constructor cannot be overridden.

17

What is the Java ClassLoader hierarchy?

ClassLoaders load .class files into JVM memory. They follow a parent-delegation model. 1. Bootstrap ClassLoader: Written in C++, loads core JDK classes (java.lang.*, java.util.*) from rt.jar / jmods. 2. Platform ClassLoader (Extension in Java 8): Loads JDK extension classes. 3. Application ClassLoader: Loads classes from the application classpath (your code and libraries). Parent delegation: Before loading a class, each ClassLoader delegates to its parent. Only loads the class itself if the parent cannot find it. This prevents malicious code from replacing core classes. Custom ClassLoaders enable dynamic loading, hot reload, and class isolation (used in OSGi, JEE containers, and Spring Boot's DevTools).

18

What is the difference between final, finally, and finalize?

final: A modifier. Applied to a variable (value cannot change), method (cannot be overridden), or class (cannot be subclassed). Makes references constants — the object itself can still be mutated unless it's immutable. finally: A block in try-catch-finally. Always executes after the try block, whether or not an exception was thrown. Used for cleanup (close resources). Note: try-with-resources is preferred over finally for AutoCloseable resources. finalize(): A method on Object, called by GC before reclaiming an object. Deprecated since Java 9 — unpredictable, can delay GC, and cause resource leaks. Use Cleaner or close() instead.

19

What are Java records (Java 16+)?

Records are immutable data carriers introduced as a standard feature in Java 16. They auto-generate a canonical constructor, private final fields, getters (no "get" prefix), equals(), hashCode(), and toString(). Syntax: public record Point(int x, int y) {} Usage: new Point(1, 2).x() // accessor — not getX() Records cannot extend other classes (implicitly extend java.lang.Record), can implement interfaces, can have static fields/methods and compact constructors (for validation). Ideal for DTOs, value objects, and response/request models. More concise than Lombok @Value.

20

What is the Executor framework and when would you use it over raw Threads?

The Executor framework (java.util.concurrent) decouples task submission from execution. Raw Thread creation is expensive — the framework maintains a pool of reusable threads. Key implementations: • Executors.newFixedThreadPool(n) — fixed size pool • Executors.newCachedThreadPool() — grows/shrinks as needed • Executors.newScheduledThreadPool(n) — schedule tasks with delay/period • ForkJoinPool — work-stealing pool for recursive tasks (used by parallel streams) Future<T> and CompletableFuture<T> allow async computation, chaining, and combining multiple async operations. Virtual Threads (Java 21 Project Loom) provide millions of lightweight threads, making thread-per-request models feasible again without the overhead of OS threads.

21

What is encapsulation and how is it achieved in Java?

Encapsulation bundles data (fields) and behavior (methods) together and restricts direct access to an object's internal state. Achieved by: • Declaring fields private • Providing public getters/setters with validation logic • Using package-private or protected access for internal APIs Benefits: You can change the internal representation without breaking external callers, enforce invariants (e.g., age must be > 0), and control read-only or write-only access. Record classes (Java 16+) enforce encapsulation by making all fields private final with auto-generated accessors.

22

What is polymorphism? Explain runtime vs compile-time polymorphism.

Polymorphism means "many forms" — the ability of one interface to be used for different underlying forms (data types). Compile-time polymorphism (static dispatch): Achieved through method overloading. The compiler selects the method based on parameter types at compile time. Runtime polymorphism (dynamic dispatch): Achieved through method overriding and inheritance. The JVM selects the method to call based on the actual object type at runtime. Example: Animal a = new Dog(); // runtime type is Dog a.speak(); // calls Dog.speak(), not Animal.speak() This is the foundation of the Open/Closed Principle — you extend behavior without modifying existing code.

23

What is the difference between composition and inheritance?

Inheritance (IS-A): A subclass extends a parent class, inheriting its state and behavior. Creates tight coupling — changes in the parent can break the child. Single inheritance only in Java. Composition (HAS-A): A class contains a reference to another object and delegates to it. Loose coupling — the contained class can be swapped. Favored in modern design. Rule: "Favor composition over inheritance" (GoF). Use inheritance only when the subtype truly IS-A supertype (Liskov Substitution). Use composition when you want to reuse behavior without imposing an IS-A relationship. Example: Instead of extending ArrayList, compose with a List<> field and expose only the operations you want.

24

What is a marker interface? Give examples.

A marker interface (also called a tag interface) has no methods or fields — it simply marks a class with a semantic tag that the JVM or framework uses to alter behavior. Examples: • Serializable: Marks a class as safe to serialize. ObjectOutputStream checks this at runtime and throws NotSerializableException if absent. • Cloneable: Marks a class as allowing Object.clone() to make a field-by-field copy. • RandomAccess: Marks a List as supporting fast O(1) index access (e.g., ArrayList). Collections.binarySearch uses this to choose algorithm. Modern alternative: Annotations (@interface) serve the same purpose more expressively and with optional metadata. Most new frameworks use annotations rather than marker interfaces.

25

What are the design patterns most commonly asked in Java interviews?

Creational: • Singleton: One instance per JVM (double-checked locking or enum-based). Used for config, thread pools. • Builder: Construct complex objects step-by-step. Used by StringBuilder, Lombok @Builder. • Factory Method / Abstract Factory: Decouple object creation from usage. • Prototype: Clone existing objects. Structural: • Adapter: Bridge incompatible interfaces. • Decorator: Add behavior dynamically (Java IO streams). • Proxy: Control access to an object (Spring AOP, JDK Proxy). • Facade: Simplified interface to a complex subsystem. Behavioral: • Strategy: Encapsulate algorithms and make them interchangeable (Comparator). • Observer: Event listener pattern (Spring ApplicationEvent). • Template Method: Define skeleton of algorithm in base class. • Command: Encapsulate a request as an object.

26

How do you implement a Singleton in Java correctly?

Three correct approaches: 1. Enum-based (best — safe, lazy, thread-safe, serialization-safe): public enum Singleton { INSTANCE; public void doWork() {} } 2. Double-checked locking (for lazy init with a class): private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) instance = new Singleton(); } } return instance; } 3. Initialization-on-demand holder (lazy, thread-safe without synchronized): private static class Holder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } Avoid: simple synchronized getInstance() (locks on every call), non-volatile double-checked locking (broken before Java 5).

27

What is the difference between ArrayList and LinkedList?

ArrayList: Backed by a dynamic array. O(1) random access by index. O(n) insertion/deletion in the middle (elements must shift). Best for: frequent reads, iteration, random access. LinkedList: Doubly-linked list. O(n) random access (must traverse). O(1) insertion/deletion at known position (just pointer updates). Also implements Deque — useful as a queue/deque. Best for: frequent head/tail operations. In practice: ArrayList is almost always faster for traversal due to CPU cache locality (contiguous memory). LinkedList has higher memory overhead (each node holds two pointers + object header). For queue semantics, prefer ArrayDeque over LinkedList — faster and no null elements.

28

What is the difference between HashSet, LinkedHashSet, and TreeSet?

All implement Set (no duplicate elements): HashSet: Backed by HashMap. O(1) add/remove/contains. No order guaranteed. Allows null. LinkedHashSet: Backed by LinkedHashMap. Maintains insertion order. Slightly slower than HashSet. Useful when you need unique elements in insertion order. TreeSet: Backed by Red-Black Tree (TreeMap). Elements sorted in natural order or by custom Comparator. O(log n) operations. Does not allow null. Use for sorted unique elements or range operations (headSet, tailSet, subSet). Key rule: equals() and hashCode() must be correctly overridden for objects stored in Hash-based collections. Comparable or Comparator required for TreeSet.

29

What is the difference between HashMap and TreeMap?

HashMap: Hash table backed. O(1) average for put/get/remove. No ordering. Allows one null key. Best for general-purpose key-value storage. TreeMap: Red-Black Tree backed. O(log n) for all operations. Maintains keys in sorted order (natural or Comparator). No null keys allowed. Supports NavigableMap methods: floorKey(), ceilingKey(), headMap(), tailMap(), subMap(). LinkedHashMap: Maintains insertion order (or access order for LRU caches). O(1) operations like HashMap. Use TreeMap when: You need sorted keys, range queries, or min/max key operations. Use LinkedHashMap for LRU cache (override removeEldestEntry()).

30

What is the contract between equals() and hashCode()?

The contract (from the Java specification): 1. If a.equals(b) is true, then a.hashCode() == b.hashCode() must be true. 2. If a.hashCode() == b.hashCode(), a.equals(b) may be true or false (hash collision is allowed). Violating this breaks HashMap and HashSet — objects that are logically equal will not be found in the map/set because they hash to different buckets. Best practice: Always override both together. IDEs and Lombok generate correct implementations. Use Objects.equals() and Objects.hash() for null-safe implementations. Example bad case: Override equals() for logical equality but forget hashCode() → two equal objects land in different buckets → map.get() returns null even though you map.put() the object.

31

What is the difference between fail-fast and fail-safe iterators?

Fail-fast iterators: Throw ConcurrentModificationException if the collection is structurally modified during iteration (other than through the iterator itself). They track a modCount and check it on every next() call. Examples: ArrayList, HashMap, HashSet iterators. Fail-safe iterators: Operate on a snapshot copy of the collection — modifications to the original during iteration don't cause exceptions. They may not reflect the most recent state. Examples: ConcurrentHashMap, CopyOnWriteArrayList iterators. Fail-safe trade-off: Higher memory (copy) and potentially stale data. Fail-fast trade-off: Exception if modified concurrently — but detects bugs early. Safe way to remove during iteration: Use Iterator.remove() with fail-fast iterators, or use List.removeIf() (Java 8+).

32

What is CopyOnWriteArrayList and when should you use it?

CopyOnWriteArrayList creates a fresh copy of the underlying array on every write operation (add, set, remove). Reads are lock-free and always consistent because they operate on a stable snapshot. Characteristics: • Thread-safe reads with no locking — very fast for read-heavy scenarios • Expensive writes — O(n) copy on every mutation • Iterators are fail-safe — no ConcurrentModificationException, but see old data • No null guarantee issues Use when: Reads vastly outnumber writes (event listener lists, observer lists, configuration lists that rarely change). Avoid when: Write operations are frequent or the list is large — copying is expensive. Similar: CopyOnWriteArraySet wraps CopyOnWriteArrayList.

33

How does PriorityQueue work and when would you use it?

PriorityQueue is a min-heap by default — poll() always removes and returns the smallest element according to natural ordering or a custom Comparator. Internally: A complete binary tree stored in an array. Parent is always smaller than children. poll() removes the root (min), swaps last element to root, sifts down. offer() adds at the end and sifts up. Both O(log n). For max-heap: PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); Use cases: • Dijkstra's algorithm (shortest path) • Event-driven simulation (process events by timestamp) • Top-K frequent elements • Merge K sorted lists • Any "always process the most important item first" scenario

34

What is the happens-before relationship in Java Memory Model?

The Java Memory Model (JMM) defines when one thread's writes are guaranteed to be visible to another thread's reads. A happens-before (HB) relationship means: all actions before A are visible to all actions after B. Guaranteed HB relationships: • Within a single thread: Actions happen in program order • Monitor unlock → subsequent lock of same monitor • volatile write → subsequent volatile read of same variable • Thread.start() → first action in the started thread • Last action in thread → Thread.join() returning • Object constructor completion → finalizer start Without HB: Reads may see stale values. A field that Thread A writes may not be visible to Thread B unless one of the above relationships exists between them.

35

What is the difference between sleep(), wait(), and yield()?

Thread.sleep(ms): Pauses the current thread for a fixed time. Does NOT release any held locks. Throws InterruptedException. A static method. Object.wait(): Must be called inside a synchronized block. Releases the lock on the object and waits until notify() or notifyAll() is called, or a timeout expires. Used for inter-thread communication (producer-consumer). Thread.yield(): Hints to the scheduler that the current thread is willing to yield its CPU time to other threads of the same or higher priority. Does not release locks. Not reliable — the scheduler may ignore it. Rarely used in practice. Thread.onSpinWait() (Java 9+): Hint for spin-wait loops that tells the CPU to optimize for busy-waiting (improves performance on HyperThreaded CPUs).

36

What are atomic classes (AtomicInteger, AtomicReference)? How do they work?

Atomic classes in java.util.concurrent.atomic provide lock-free, thread-safe operations using hardware-level Compare-And-Swap (CAS) instructions. CAS: "If the current value == expected, update to new value atomically." If the CAS fails (another thread changed the value), retry in a loop. Key classes: • AtomicInteger, AtomicLong: Thread-safe int/long with incrementAndGet(), compareAndSet(), getAndAdd() • AtomicBoolean: Thread-safe boolean flag • AtomicReference<T>: Thread-safe object reference swap • AtomicStampedReference: Solves ABA problem by pairing value with a stamp (version counter) • LongAdder/LongAccumulator: Higher throughput than AtomicLong under high contention (uses striped counters, reduces CAS retries) Use AtomicLong for simple counters; LongAdder when update throughput is critical.

37

What is a CountDownLatch vs CyclicBarrier?

Both are synchronization aids but serve different purposes: CountDownLatch: A counter initialized with N. Threads call countDown() to decrement. Other threads block on await() until the counter reaches 0. One-time use — cannot be reset. Use for: waiting for N services to start before accepting traffic, waiting for N tasks to complete. CyclicBarrier: N threads all call await() and block until all N have arrived at the barrier. When all arrive, all are released simultaneously. Can be reused (cyclic). Optional barrier action runs when all arrive. Use for: phased parallel computation where each phase must complete before the next starts. Semaphore: Maintains a count of permits. acquire() decrements (blocks if 0), release() increments. Use for: rate limiting, limiting concurrent access to a resource pool.

38

What is a ThreadLocal and what are its dangers?

ThreadLocal<T> provides a separate variable instance for each thread. Each thread has its own independently initialized copy of the variable. Useful for per-thread state without synchronization. Common uses: • SimpleDateFormat (not thread-safe) — each thread gets its own instance • Database connections, transaction context • User context / RequestAttributes in Spring web (RequestContextHolder uses ThreadLocal) • MDC in logging (Mapped Diagnostic Context for per-request log correlation) Dangers: • Memory leaks in thread pool environments: If ThreadLocal.remove() is not called, the value lives as long as the thread — which in a thread pool is forever. This causes memory leaks and stale data across requests. • InheritableThreadLocal: Child threads inherit parent's value at creation time — not updated if parent changes later. Rule: Always call ThreadLocal.remove() in a try-finally block after use.

39

What is a CompletableFuture and how does it improve async programming?

CompletableFuture<T> (Java 8+) enables non-blocking async composition. It combines Future (result of async computation) with a fluent API for chaining, combining, and handling errors. Key methods: • supplyAsync(supplier) — run in ForkJoinPool, return result • thenApply(fn) — transform result (sync) • thenApplyAsync(fn) — transform in another thread • thenCompose(fn) — flatMap (chain futures) • thenCombine(other, fn) — combine two independent futures • allOf(futures...) — wait for all • anyOf(futures...) — take first completed • exceptionally(fn) — handle exceptions • handle(fn) — handle both result and exception Example: Fetch user and orders in parallel: CompletableFuture.allOf(userFuture, ordersFuture) .thenRun(() -> render(userFuture.join(), ordersFuture.join()))

40

What are virtual threads (Project Loom, Java 21)?

Virtual threads are lightweight threads managed by the JVM, not the OS. The JVM can create millions of virtual threads because they don't map 1:1 to OS threads. How it works: Virtual threads run on a pool of platform (OS) threads called carriers. When a virtual thread blocks on I/O, the carrier thread is released to run other virtual threads — no thread is wasted waiting. Benefit: Thread-per-request model becomes feasible again. Previously, with expensive OS threads, you needed async/reactive programming (WebFlux). With virtual threads, synchronous blocking code scales like reactive code. Creation: Thread.ofVirtual().start(runnable) or Executors.newVirtualThreadPerTaskExecutor(). Limitations: Synchronized blocks pin the carrier thread (use ReentrantLock instead). CPU-bound tasks don't benefit — virtual threads shine for I/O-bound workloads.

41

What is the difference between JDK, JRE, and JVM?

JVM (Java Virtual Machine): The runtime engine that executes Java bytecode. Platform-specific (different implementations for Windows, Linux, macOS). Handles memory management, GC, JIT compilation. JRE (Java Runtime Environment): JVM + standard class libraries (java.util, java.io, etc.). Enough to run Java applications. Removed as a separate download since Java 11 (OpenJDK ships only JDK). JDK (Java Development Kit): JRE + development tools: javac (compiler), javap (disassembler), jshell, jcmd, jmap, jstack, jar, javadoc. HotSpot: Oracle/OpenJDK's JVM implementation. Includes JIT compiler (C1 for fast startup, C2 for peak performance). GraalVM is an alternative JVM with ahead-of-time native compilation.

42

How does the JIT (Just-In-Time) compiler work?

Initially, the JVM interprets bytecode line by line — slow but fast to start. The JIT compiler monitors "hot" code (methods called frequently) and compiles them to native machine code at runtime. HotSpot has two JIT compilers: • C1 (Client): Fast compilation, fewer optimizations. Used initially for quick startup. • C2 (Server): Slow, heavy optimization — inlining, loop unrolling, escape analysis, dead code elimination. Applied to the hottest methods. Tiered Compilation (default since Java 8): Starts with C1, promotes to C2 as methods get hotter. Escape analysis: If an object doesn't escape the method (no references escape), the JIT can allocate it on the stack (faster, no GC) or eliminate it entirely. GraalVM native image: AOT (Ahead-of-Time) compilation to a native binary — instant startup, no JIT warmup. Used in serverless functions.

43

What is the difference between minor GC, major GC, and full GC?

Generational GC hypothesis: Most objects die young. The heap is divided into Young and Old generations. Minor GC (Young GC): Collects the Young Generation (Eden + two Survivor spaces). Very frequent and fast. Uses copying collection — surviving objects are copied to a Survivor space, then promoted to Old Gen after several GCs. Major GC (Old GC): Collects the Old Generation where long-lived objects reside. Less frequent but slower. Often concurrent (G1, ZGC, Shenandoah do most work concurrently). Full GC: Collects the entire heap (Young + Old + Metaspace). Causes a stop-the-world pause for the whole heap. Triggered by: promotion failure (Old Gen full), explicit System.gc(), or MetaSpace full. Goal: Avoid Full GC in production — tune heap sizes and choose the right GC algorithm.

44

What tools do you use to diagnose JVM memory issues?

Key tools: • jmap: Heap dump (jmap -dump:format=b,file=heap.hprof <pid>) and histogram (jmap -histo <pid>) • jstack: Thread dump — shows all threads, their state, and stack traces. Essential for diagnosing deadlocks and thread starvation. • jcmd: Swiss-army tool: GC.heap_info, Thread.print, VM.flags, VM.version • jstat: JVM statistics — GC activity, heap utilization in real time • VisualVM / JConsole: GUI tools for live JVM monitoring • Flight Recorder (JFR) + Mission Control: Low-overhead continuous profiling, includes GC events, method hot spots, locking Analyzing heap dumps: Load into Eclipse MAT or VisualVM — find retained heap, dominator tree, duplicate strings, large object arrays. Typical flow: jstack for thread issues, jmap + MAT for memory leaks, JFR for CPU/GC profiling.

45

What is the String pool and how does string interning work?

The String pool (String Table) is a special cache area in the Heap (since Java 7) where string literals are stored. When you write String s = "hello", the JVM checks the pool first — if "hello" already exists, the same object is returned. Interning: String.intern() adds a string to the pool (or returns the existing pooled instance). After interning, == comparison works for value equality. Memory: The pool uses a hash table. Too many interned strings can cause memory pressure. Don't blindly intern every string. String s1 = "hello" // pool String s2 = "hello" // same pool object, s1 == s2 is true String s3 = new String("hello") // heap, NOT pool, s1 == s3 is false String s4 = s3.intern() // s1 == s4 is true Java 9+: Compact Strings — strings use byte[] instead of char[] when all chars fit in Latin-1, halving memory for ASCII strings.

46

What is type erasure in Java generics?

Type erasure means generic type information is removed at compile time. The JVM sees no generics — they are a compile-time safety feature only. After erasure: List<String> becomes List, T becomes Object (or the bound type). The compiler inserts casts where needed. Consequences: • Cannot do: new T(), new T[], instanceof List<String> • Cannot overload: void process(List<String>) and void process(List<Integer>) — both erase to void process(List) • Generic type info not available at runtime via reflection on fields (only via Class literals or TypeToken pattern) Why? Backward compatibility with pre-generics Java code. Workaround for T.class: Pass Class<T> as a method parameter (super type token pattern, used by Gson and Jackson).

47

What is the difference between <? extends T> and <? super T>?

These are bounded wildcards in Java generics. Remember: PECS — Producer Extends, Consumer Super. <? extends T> (upper bound): The wildcard is T or any subtype of T. You can READ from it (get returns T or subtype). You CANNOT write to it (compiler doesn't know the exact subtype). List<? extends Number> — you can get Number, cannot add Integer or Double. <? super T> (lower bound): The wildcard is T or any supertype of T. You can WRITE T into it. Reading returns Object. List<? super Integer> — you can add Integer, get returns Object. Example: Collections.copy(dest, src): • src is List<? extends T> (producer — reads from it) • dest is List<? super T> (consumer — writes to it)

48

What are sealed classes (Java 17)?

Sealed classes restrict which classes can extend or implement them. The permitted subclasses must be listed explicitly. Syntax: public sealed class Shape permits Circle, Rectangle, Triangle {} public final class Circle extends Shape { ... } public non-sealed class Rectangle extends Shape { ... } // can be extended further public sealed class Triangle extends Shape permits ... {} Benefits: • Exhaustive pattern matching: The compiler knows all possible subtypes, enabling switch expressions to enforce exhaustiveness without a default case. • Better modeling of closed hierarchies (e.g., Result<T> = Success | Failure) • Pairs perfectly with records for algebraic data types (ADTs) switch (shape) { case Circle c -> ... case Rectangle r -> ... case Triangle t -> ... // no default needed — compiler knows all cases

49

What is pattern matching for instanceof (Java 16)?

Traditional instanceof required a cast after the check: if (obj instanceof String) { String s = (String) obj; ... } Pattern matching combines the check and binding: if (obj instanceof String s) { s.toUpperCase(); } // s is in scope here This eliminates the redundant cast and reduces boilerplate. In switch expressions (Java 21 — pattern matching for switch): switch (obj) { case Integer i -> "int: " + i case String s -> "str: " + s case null -> "null" default -> "other" } Guarded patterns: case Integer i when i > 0 -> "positive int" This enables writing expressive, safe type-dispatch code that the compiler verifies for completeness with sealed classes.

50

What are text blocks (Java 15)?

Text blocks are multi-line string literals that avoid the need for escape sequences and string concatenation. Syntax: Three double quotes open and close the block. The opening """ must be followed by a newline. String json = """ { "name": "Java", "version": 21 } """; Features: • Incidental whitespace stripping: The compiler strips leading whitespace based on the least-indented line. • Escape sequences like \n still work. Use \s to preserve trailing space. • String::formatted() works: """%n items: %d""".formatted(count) Ideal for: SQL queries, JSON/XML payloads, HTML templates, log messages — any multi-line string that was ugly with + concatenation.

51

What is the difference between map() and flatMap() in streams?

map(Function<T, R>): Transforms each element to exactly one element. Returns Stream<R>. List<String> upper = list.stream().map(String::toUpperCase).toList(); flatMap(Function<T, Stream<R>>): Each element is mapped to zero or more elements (a stream). The resulting streams are flattened into one stream. List<String> words = sentences.stream() .flatMap(s -> Arrays.stream(s.split(" "))) .toList(); Optional equivalent: flatMap on Optional unwraps an Optional<Optional<T>> to Optional<T>. Optional<String> city = getUser().flatMap(User::getAddress).flatMap(Address::getCity); Analogy: map is 1-to-1 transformation; flatMap is 1-to-many, then flattening.

52

What are method references in Java 8?

Method references are shorthand for lambdas that call a specific method. They improve readability. Four types: 1. Static method: ClassName::staticMethod Function<String, Integer> f = Integer::parseInt; 2. Instance method of a specific instance: instance::method Consumer<String> printer = System.out::println; 3. Instance method of an arbitrary instance (first param is receiver): ClassName::method Function<String, String> upper = String::toUpperCase; // equivalent to: s -> s.toUpperCase() 4. Constructor: ClassName::new Supplier<List<String>> listFactory = ArrayList::new; Method references are not always clearer — use them when the method name communicates intent better than a lambda body.

53

What is the difference between Comparable and Comparator?

Comparable<T>: The class itself defines its natural ordering by implementing compareTo(T other). Only one ordering per class. Used by: Collections.sort(), TreeSet/TreeMap (natural order), Arrays.sort(). public class Student implements Comparable<Student> { public int compareTo(Student o) { return this.gpa.compareTo(o.gpa); } } Comparator<T>: An external comparison strategy. Multiple comparators can exist for the same class. Java 8+ Comparator.comparing() builder: Comparator<Student> byName = Comparator.comparing(Student::getName); Comparator<Student> byGpaThenName = Comparator.comparingDouble(Student::getGpa) .thenComparing(Student::getName); Use Comparable for the class's "natural" order. Use Comparator when you need multiple orderings or can't modify the class.

54

What is Stream.collect() and what are common Collectors?

collect() is a terminal operation that accumulates stream elements into a mutable result container. Common Collectors: • toList() / toSet() / toUnmodifiableList() • toMap(keyMapper, valueMapper) — throws on duplicate keys; use mergeFunction for duplicates • groupingBy(classifier) → Map<K, List<T>> • groupingBy(classifier, counting()) → Map<K, Long> • partitioningBy(predicate) → Map<Boolean, List<T>> • joining(", ", "[", "]") — concatenate strings • summarizingInt() → IntSummaryStatistics (count, sum, min, max, avg) • mapping(mapper, downstream) — transform before collecting • teeing(col1, col2, merger) (Java 12) — collect to two collectors simultaneously Example: Group students by grade: Map<String, List<Student>> byGrade = students.stream() .collect(Collectors.groupingBy(Student::getGrade));

55

What is try-with-resources and how does it work?

Try-with-resources (Java 7+) automatically closes resources that implement AutoCloseable when the try block exits — whether normally or via exception. Syntax: try (InputStream is = new FileInputStream(file); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { // use br } // is and br are automatically closed in reverse order Suppressed exceptions: If both the try block and close() throw exceptions, the close() exception is suppressed and accessible via Throwable.getSuppressed(). In traditional try-finally, the finally exception would silently swallow the original exception. Multiple resources are closed in reverse declaration order (LIFO). Use try-with-resources for: streams, connections, channels, sockets, anything Closeable. Avoid manual try-finally for resource management.

56

What is the difference between throw and throws?

throws (declaration): Appears in the method signature. Declares that a method may throw the listed checked exceptions, warning callers to handle them. void readFile(String path) throws IOException { ... } throw (statement): Actually throws an exception object at runtime. throw new IllegalArgumentException("Invalid input: " + value); Key rules: • Checked exceptions: Must be declared with throws or caught. Unchecked (RuntimeException) do not need to be declared. • You can throw an unchecked exception without declaring it in the signature. • throw rethrows — inside a catch block, throw e; or throw new RuntimeException("context", e); to preserve the original stack trace as cause. • Multi-catch (Java 7): catch (IOException | SQLException e) — avoids duplicating handler code.

57

What is exception chaining and why is it important?

Exception chaining (wrapping) preserves the original cause when rethrowing exceptions across layers. catch (SQLException e) { throw new RepositoryException("Failed to save user", e); // e is the cause } The original SQLException is attached as the cause and appears in the stack trace when logging: log.error("...", e) // logs both the wrapper and the original cause e.getCause() // returns the original SQLException Why it matters: • Without chaining, you lose the original root cause when catching and rethrowing • Makes debugging production issues possible — you see the full chain from high-level business error down to the JDBC driver exception • Never do: catch (Exception e) { throw new RuntimeException("failed"); } — loses the original cause

58

What is the difference between Java IO and NIO?

Java IO (java.io): Stream-based, blocking I/O. One thread per connection is tied up waiting for I/O. Simple to use. Suitable for low-concurrency, simple file/socket operations. Java NIO (java.nio, Java 4+): Buffer-based, channel-oriented, supports non-blocking and multiplexed I/O. • Channels: Full-duplex (read+write) connections • Buffers: Data read/written in chunks (not byte-by-byte) • Selectors: Single thread monitors multiple channels for readiness — scalable for many connections NIO.2 (Java 7): java.nio.file.Path API replaces java.io.File. Files.copy(), Files.walk(), WatchService for directory change events. Much richer API. For most applications: Use NIO.2 for file operations (Path/Files). Use Netty or Vert.x for high-concurrency network I/O — they wrap NIO with a better API. Java 21 virtual threads make blocking IO scalable, reducing the need for NIO for most use cases.

59

What is Java Serialization and what are its pitfalls?

Java Serialization converts an object graph to a byte stream (ObjectOutputStream) and back (ObjectInputStream). The class must implement Serializable. serialVersionUID: A version field that must match between the serialized form and the current class. If missing, the JVM generates one based on class structure — changing the class breaks deserialization. Pitfalls: • Security: Deserialization of untrusted data is a critical RCE vulnerability (CVE-2015-4852 — Apache Commons Collections). Never deserialize untrusted bytes without a filter. • Brittleness: Any class change can break existing serialized data in production. • transient fields: Not serialized. Reconstructed to default values on deserialization. • Performance: Slow compared to JSON, Protobuf, Avro. Modern practice: Use JSON (Jackson), Protobuf, or Avro instead of Java Serialization for external data exchange. Use Java Serialization only for internal caching where you control both sides.

60

What is reflection in Java and when is it used?

Reflection allows inspecting and manipulating classes, methods, fields, and constructors at runtime, even private ones. Key classes: Class<?>, Method, Field, Constructor (in java.lang.reflect). Common uses: • Frameworks: Spring uses reflection to inject dependencies, discover annotations, and call lifecycle methods. • ORM: Hibernate uses reflection to map fields to columns. • Testing: Mockito uses reflection+bytecode to create mocks. • Serialization: Jackson uses reflection to discover fields/getters. Downsides: • Performance: Slower than direct method calls (though invokedynamic and MethodHandles mitigate this). • Security: Can bypass access modifiers — requires setAccessible(true) which may be restricted by modules. • Refactoring brittleness: Reflection on field/method names breaks when renamed. Java 9+ modules restrict reflection by default — requires module opens declarations.

61

What is the difference between deep copy and shallow copy?

Shallow copy: Copies the object but not the objects it references. The copy and original share the same referenced objects. Modifying a nested object affects both. Deep copy: Recursively copies all objects in the object graph. The copy is completely independent. Achieving deep copy: 1. Override clone() and clone all mutable fields (tedious, error-prone) 2. Serialization round-trip: serialize to bytes and deserialize (slow) 3. Copy constructors: new Order(existingOrder) — manually copy all fields 4. Builder pattern: Build a new object from the existing one's values 5. Libraries: Apache Commons BeanUtils, Orika, MapStruct In practice: Prefer copy constructors or builders for clarity. Avoid Object.clone() — it has a broken contract and is hard to implement correctly for complex hierarchies.

62

What is autoboxing and unboxing? What are the performance implications?

Autoboxing: Automatic conversion of primitives to their wrapper objects (int → Integer) by the compiler. Integer i = 42; // compiler converts to Integer.valueOf(42) Unboxing: Automatic conversion of wrappers to primitives (Integer → int). int x = myInteger; // compiler inserts myInteger.intValue() Caching: Integer.valueOf() caches instances for values -128 to 127. Within this range, == comparison works for cached instances — but don't rely on this for equality checks. Performance pitfalls: • Boxing in tight loops: List<Integer> in a loop boxes every element — use IntStream or int[] for performance-critical paths. • NullPointerException: Unboxing a null wrapper throws NPE. • Map counting: map.put(key, map.getOrDefault(key, 0) + 1) — boxes and unboxes on every call. Use compute() or switch to int-based collections (IntIntHashMap from Eclipse Collections).

63

What are enums in Java and what advanced features do they support?

Enums are a special class type where instances are a fixed set of constants. They implicitly extend java.lang.Enum and cannot be subclassed. Advanced features: • Fields and methods: Each enum can have fields, constructors, and methods. • Abstract methods: Each constant can override an abstract method differently (strategy pattern). • Interface implementation: Enums can implement interfaces. • EnumSet/EnumMap: Highly efficient (bit-vector and array-backed) alternatives to HashSet/HashMap for enum keys. • Singleton pattern: The best Singleton implementation (see Effective Java Item 3). Example: public enum Planet { MERCURY(3.303e+23, 2.4397e6), EARTH(5.976e+24, 6.37814e6); private final double mass, radius; Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } double surfaceGravity() { return G * mass / (radius * radius); } }

64

What is the difference between static and instance initializer blocks?

Static initializer block: Runs once when the class is loaded by the ClassLoader. Executes before any constructor. Used to initialize static fields with complex logic. static { // static block map = new HashMap<>(); map.put("key", "value"); } Instance initializer block: Runs before every constructor, after the super() call. Copied into every constructor by the compiler. Useful for common initialization across multiple constructors. { // instance block list = new ArrayList<>(); list.add("default"); } Execution order: 1. Parent static initializers (top to bottom) 2. Child static initializers 3. Parent instance initializers + constructor 4. Child instance initializers + constructor In practice: Prefer field initializers (int x = 5) over initializer blocks for clarity.

65

What is the difference between String.format() and String.formatted()?

Both produce formatted strings from a format string with placeholders. String.format(): Static method. String.format("Hello %s, you are %d", name, age). String.formatted() (Java 15+): Instance method on String. "Hello %s, you are %d".formatted(name, age). More readable in method chains and with text blocks. Common format specifiers: %s (string), %d (integer), %f (float), %n (platform line separator), %.2f (2 decimal places), %10d (right-aligned width 10), %-10s (left-aligned). Performance: For simple cases, + concatenation or StringBuilder is faster. For logging, use SLF4J's {} parameterized logging instead of String.format() — the format string is only evaluated if the log level is enabled.

66

What is the var keyword (Java 10) and when should you use it?

var enables local variable type inference — the compiler infers the type from the initializer. It's still statically typed; the type is determined at compile time. var list = new ArrayList<String>(); // inferred as ArrayList<String> var entry = map.entrySet().iterator().next(); // cleaner than Map.Entry<String, Integer> Allowed: • Local variables with initializer • For-each loop variables • Try-with-resources variables Not allowed: Method parameters, return types, fields, without initializer. Guidelines: • Use when the type is obvious from the right-hand side (new ArrayList<>(), factory methods) • Avoid when it reduces clarity (var x = getValue(); — what type is returned?) • Especially useful with long generic types: var futures = new HashMap<String, CompletableFuture<List<Order>>>()

67

What are Java modules (Java 9 — JPMS)?

The Java Platform Module System (JPMS) introduced strong encapsulation and explicit dependency declarations at the module level. module-info.java: module com.example.app { requires com.example.core; // declare dependency exports com.example.api; // expose public API opens com.example.model; // allow reflection (for frameworks) provides SomeService with Impl; // service provider uses SomeService; // service consumer } Benefits: • Strong encapsulation: Internal packages are hidden by default (not just by access modifiers) • Reliable configuration: Missing modules fail at startup, not at runtime • Smaller runtime images with jlink (only include needed modules) In practice: Application developers rarely write module-info.java (complex to get right with libraries). Framework/library developers and those using jlink for cloud-native images benefit most.

68

What is the difference between an inner class, static nested class, and anonymous class?

Inner class (non-static nested class): Defined inside another class. Has implicit reference to the enclosing instance. Can access all members (including private) of the outer class. Each instance requires an outer class instance. Static nested class: Defined with static keyword inside another class. No implicit reference to enclosing instance. Accessed as OuterClass.NestedClass. Use for logically grouping helper classes. Anonymous class: A one-time, nameless class defined and instantiated in one expression. Used for interface implementations before lambdas (now mostly replaced). Can capture effectively-final local variables. Local class: Named class defined inside a method. Rarely used. Best practice: Prefer static nested classes over inner classes — inner classes hold a reference to the outer instance, which can cause memory leaks (especially in Android). Use lambdas instead of anonymous classes for functional interfaces.

69

What are the new switch expressions (Java 14)?

Switch expressions are an enhanced form of the switch statement that can return a value and use the arrow (→) syntax. Arrow form (no fall-through): String result = switch (day) { case MONDAY, TUESDAY -> "Weekday"; case SATURDAY, SUNDAY -> "Weekend"; default -> "Other"; }; Yield (for multi-statement branches): int value = switch (input) { case "A" -> 1; case "B" -> { int v = compute(input); yield v * 2; // use yield to return from a block } default -> 0; }; Benefits: No accidental fall-through, exhaustiveness checked with sealed classes/enums, expression form eliminates repetitive variable assignment, cleaner than if-else chains.

70

What is the difference between Runnable, Callable, and Future?

Runnable: A task with no return value and no checked exception. void run(). Used with Thread, ExecutorService.execute(). Callable<V>: A task that returns a value and can throw a checked exception. V call() throws Exception. Used with ExecutorService.submit() which returns a Future<V>. Future<V>: A handle to an async computation. Key methods: • get() — blocks until result is available (throws ExecutionException wrapping the task's exception) • get(timeout, unit) — blocks with timeout • isDone() — non-blocking check • cancel(mayInterrupt) — attempt to cancel • isCancelled() Future limitations: No callback, no composition, no exception handling without get(). These are solved by CompletableFuture<V> which adds thenApply, thenCompose, exceptionally, and more.

71

What is the difference between String.equals() and String.equalsIgnoreCase()?

equals(): Case-sensitive comparison. "Hello".equals("hello") is false. equalsIgnoreCase(): Case-insensitive. "Hello".equalsIgnoreCase("hello") is true. Internally converts both strings to lowercase (locale-aware) and compares. Other useful String methods: • compareTo() / compareToIgnoreCase() — lexicographic comparison (for sorting) • contains(CharSequence) — substring check • startsWith(prefix) / endsWith(suffix) • indexOf(str) — first occurrence index, -1 if not found • replace(old, new) / replaceAll(regex, replacement) • strip() (Java 11): Unicode-aware whitespace trim. Prefer over trim() which only handles ASCII whitespace. • isBlank() (Java 11): True if empty or contains only whitespace • repeat(n) (Java 11): "ab".repeat(3) → "ababab" • lines() (Java 11): Returns Stream<String> of lines

72

What is the difference between Iterator and ListIterator?

Iterator: Forward-only traversal for any Collection. Methods: hasNext(), next(), remove() (removes current element). ListIterator: Bidirectional traversal for List only. Extends Iterator. Additional methods: • hasPrevious(), previous() — backward traversal • add(element) — insert at current position • set(element) — replace current element • nextIndex(), previousIndex() — current cursor position Usage: ListIterator<String> it = list.listIterator(list.size()); // start from end while (it.hasPrevious()) { System.out.println(it.previous()); } Modern alternative: For simple forward iteration, use for-each (which uses Iterator internally) or stream().forEach(). Use ListIterator only when you specifically need index info or backward traversal.

73

What is the difference between abstract class and concrete class?

Concrete class: A fully implemented class that can be instantiated directly with new. All methods have implementations. Abstract class: Declared with abstract keyword. Cannot be instantiated directly. May contain: • Abstract methods (no body): Subclasses must implement them • Concrete methods: Shared implementation • State (instance variables): Subclasses inherit it Purpose: Define a template where the overall algorithm is in the abstract class but specific steps are delegated to subclasses (Template Method pattern). Example: AbstractList in Java Collections — provides a skeletal implementation; concrete subclasses (ArrayList, LinkedList) override only the required methods (get, size, set, add, remove). Difference from interface: Abstract class can have state, non-public methods, and constructors. A class can only extend one abstract class but implement multiple interfaces.

74

How do you handle NullPointerException in Java?

Prevention strategies: 1. Optional<T>: Explicitly model nullable return values. Avoids null checks in callers. 2. Objects.requireNonNull(param, "message"): Fail-fast validation in constructors and method entries. Throws NullPointerException with a clear message immediately rather than later. 3. Null Object pattern: Return a default no-op object instead of null. 4. @NonNull / @Nullable annotations: Document nullability intent. Static analysis tools (IntelliJ, SpotBugs, Checker Framework) warn about potential NPEs at compile time. 5. Avoid returning null from methods: Return empty collections (Collections.emptyList()), Optional.empty(), or throw meaningful exceptions. Java 14+ Helpful NullPointerExceptions: NPE messages now tell you exactly which field/variable was null ("Cannot invoke 'User.getName()' because 'user' is null").

75

What is the Collections utility class? What are its most important methods?

java.util.Collections is a utility class with static methods for working with collections. Key methods: • Collections.sort(list) — sort using natural order • Collections.sort(list, comparator) — sort with custom order • Collections.binarySearch(list, key) — O(log n) search (list must be sorted) • Collections.reverse(list) — reverse in place • Collections.shuffle(list) — random shuffle • Collections.min(collection) / max(collection) • Collections.frequency(collection, element) — count occurrences • Collections.disjoint(c1, c2) — true if no common elements • Collections.unmodifiableList(list) — read-only view (not deep immutable) • Collections.synchronizedList(list) — thread-safe wrapper (coarse-grained locking) • Collections.singleton(e) — immutable single-element set • Collections.nCopies(n, e) — list of n copies of e • Collections.emptyList() / emptySet() / emptyMap()

76

What is the difference between Deque and Queue?

Queue: FIFO (First In, First Out) interface. Insert at tail, remove from head. • offer(e) — insert (returns false if full) • poll() — remove head (returns null if empty) • peek() — inspect head without removing Deque (Double-Ended Queue): Extends Queue. Elements can be added/removed from both ends. • offerFirst(e), offerLast(e) • pollFirst(), pollLast() • peekFirst(), peekLast() Use as a stack: push(e) = addFirst(), pop() = removeFirst() — prefer over Stack class (which is synchronized and extends Vector). Implementations: • ArrayDeque: Array-backed, resizing. O(1) amortized at both ends. Preferred for both queue and stack use cases. No null elements. • LinkedList: Linked nodes. More memory overhead. Allows null. • PriorityQueue: Queue with priority ordering (not a deque).

77

What is a WeakHashMap and when would you use it?

WeakHashMap is a Map implementation where keys are held using WeakReferences. When a key has no strong references outside the map (i.e., only the map holds a weak reference), it becomes eligible for garbage collection. The entry is automatically removed when the key is GC'd. Use case: Caches where the value should live only as long as the key is alive. The map won't prevent GC of the key — it doesn't "anchor" it in memory. Example: A per-Class metadata cache. If the class is unloaded (rare in normal JVMs), its entry is removed automatically. Limitations: Not thread-safe. Iteration may encounter entries that are being GC'd. Don't use it as a general-purpose cache — Guava Cache or Caffeine (with weak keys/values option) are better for caching with expiration, loading, and statistics. Reference types: Strong (default), Soft (GC'd when memory is low — good for caches), Weak (GC'd on next GC), Phantom (for cleanup actions).

78

What is the difference between checked and unchecked exceptions — best practices for APIs?

Design guidelines for exception types in APIs: Use checked exceptions when: The caller can reasonably be expected to recover from the failure and you want to force them to handle it. Example: FileNotFoundException — the caller might try a different path. Use unchecked exceptions when: The failure represents a programming error (wrong argument, null where not expected) or an unrecoverable system error. Forcing callers to catch these pollutes APIs with boilerplate. Modern trend (Spring, Hibernate, JPA): Wrap all persistence-specific checked exceptions in unchecked DataAccessException — reduces try-catch noise in service layer. Best practices: • Provide meaningful messages with context: throw new IllegalArgumentException("Age must be positive, got: " + age) • Use specific exception types, not Exception or RuntimeException • Document with @throws in Javadoc • Prefer translating low-level exceptions at layer boundaries (DAO → Service → Controller)

79

What is the ternary operator and the null-coalescing equivalent in Java?

Ternary operator: condition ? valueIfTrue : valueIfFalse String label = count > 1 ? "items" : "item"; Nested ternary: Avoid — becomes unreadable quickly. Use if-else or switch expression instead. Null-coalescing: Java doesn't have the ?? operator (like C# or Kotlin). Equivalents: • Traditional: String name = input != null ? input : "default"; • Optional: Optional.ofNullable(input).orElse("default") • Objects.requireNonNullElse(input, "default") (Java 9+) • Objects.requireNonNullElseGet(input, () -> expensive()) (Java 9+, lazy supplier) For method chaining with nulls, Optional or Objects.toString(obj, "default") are idiomatic. Note: Java 21 preview includes null-safe patterns in switch — the language is gradually improving null handling.

80

What is the difference between == null check and Objects.isNull()?

Both check for null but in different contexts: == null: Direct comparison. The standard idiom for null checks. Clear and zero-overhead. if (user == null) return; Objects.isNull(obj) / Objects.nonNull(obj): Introduced for use as method references in streams. list.stream().filter(Objects::nonNull).toList(); They are equivalent to x == null and x != null respectively. Objects utility class other useful methods: • Objects.requireNonNull(obj, "msg") — null-guard with exception • Objects.requireNonNullElse(obj, default) — null-safe default • Objects.toString(obj, "default") — null-safe toString • Objects.equals(a, b) — null-safe equals (Objects.equals(null, null) is true) • Objects.hash(a, b, c) — null-safe combined hash code • Objects.deepEquals(a, b) — deep array equality

81

What is the difference between interface default methods and abstract class methods?

Since Java 8, interfaces can have default methods — concrete methods with a body. This enables adding new methods to interfaces without breaking existing implementations. Interface default method: • public by default, no state access (no instance fields) • Can be overridden by implementing classes • If two interfaces provide default methods with the same signature, the implementing class must override to resolve the conflict Abstract class concrete method: • Can access instance fields and state • Can be any visibility • Can call other instance methods • Only one class can be extended (no multiple inheritance) Decision: If you need shared state or non-public helper methods, use an abstract class. If you only need default behavior that implementing classes can override, use interface default methods. Prefer interfaces for broader type compatibility.

82

What is the difference between Iterable and Iterator?

Iterable<T>: A collection-like type that can provide an Iterator. Has one method: iterator(). Implementing Iterable allows your type to be used in for-each loops. Iterator<T>: The actual cursor that traverses elements. Has: hasNext() (check), next() (advance + return), remove() (optional — remove current). Relationship: A for-each loop compiles to: Iterator<T> it = collection.iterator(); while(it.hasNext()) { T e = it.next(); ... } Why separate? Separates the traversal mechanism from the data source. One Iterable can produce multiple independent Iterators (each with its own cursor). Streams and Spliterators are more powerful modern alternatives — Spliterator supports parallel traversal and characteristic hints.

83

What are the best practices for writing clean Java code?

Key principles: Naming: Meaningful, self-documenting names. Classes: noun (UserService). Methods: verb (getUser, validateEmail). Constants: UPPER_SNAKE. Boolean: isActive, hasPermission. Methods: Small, single responsibility. If you need to comment what it does, extract a method. Limit parameters (>4 → use a parameter object or Builder). Immutability: Prefer immutable objects (final fields, no setters). Use Java records for data carriers. Collections: return unmodifiable copies. Null handling: Return Optional instead of null from methods. Fail fast with requireNonNull at boundaries. Error handling: Use specific exceptions with context. Log at the right level. Code smells to avoid: Magic numbers (use named constants), deep nesting (extract methods, use early return), mutable shared state, God classes. Tools: Checkstyle, PMD, SpotBugs, SonarQube for automated enforcement. Apply consistently.

84

What is the difference between a process and a thread in Java?

Process: An independent program in execution with its own memory space (heap, stack, code, data). Processes communicate via IPC (sockets, pipes, shared memory). High isolation — one process crashing doesn't affect others. Thread: A unit of execution within a process. All threads share the process's heap memory. Each thread has its own stack. Lightweight to create (compared to processes). Threads communicate via shared memory — which requires careful synchronization. In Java: • A JVM runs as a process • Threads are created with Thread, Runnable, or ExecutorService • Java 21 adds virtual threads (lightweight, managed by JVM, millions possible) • ProcessBuilder: Spawns OS processes from Java (for running external commands) Context switching: Threads switch faster than processes. Virtual threads switch even faster (no OS involvement).

85

What is a race condition and how do you prevent it?

A race condition occurs when program behavior depends on the relative timing of thread execution — the outcome varies unpredictably when threads access shared mutable state without proper synchronization. Example: Two threads doing counter++ simultaneously. Each reads the same value, increments, and writes — one increment is lost. Prevention: 1. Synchronized blocks/methods: Mutex-based mutual exclusion 2. Atomic classes: AtomicInteger.incrementAndGet() — CAS-based, lock-free 3. Locks: ReentrantLock.lock() / unlock() 4. Immutable objects: If state never changes, no race condition possible 5. Thread confinement: Only one thread accesses the data (ThreadLocal, actor model) 6. Concurrent collections: ConcurrentHashMap, CopyOnWriteArrayList Detection: Difficult — races are non-deterministic. Tools: ThreadSanitizer, Java Flight Recorder, stress testing with many threads, code review with happens-before analysis.

86

What is the difference between HashMap.computeIfAbsent() and getOrDefault()?

getOrDefault(key, defaultValue): Returns the existing value if the key exists, otherwise returns the defaultValue WITHOUT inserting it into the map. The map is unchanged. computeIfAbsent(key, mappingFunction): If the key is absent, calls the mapping function to compute a new value, INSERTS it into the map, and returns it. If the key exists, returns the existing value without calling the function. Use case comparison: // Read-only default, don't store: String v = map.getOrDefault("key", "default"); // Lazy init — compute and store only if absent: List<String> list = map.computeIfAbsent("key", k -> new ArrayList<>()); list.add("item"); // This add is reflected in the map Other compute methods: • compute(key, (k,v) -> newValue): Always computes, null return removes key • computeIfPresent(key, fn): Only if key exists • merge(key, value, mergeFunction): Merge existing and new value

87

What is the purpose of the transient keyword?

transient marks a field to be excluded from Java Serialization. When an object is serialized, transient fields are not written to the stream. When deserialized, they are set to their default values (null for objects, 0 for primitives, false for booleans). Use cases: • Fields that can be re-derived: A cached computed value that can be recalculated after deserialization. • Non-serializable fields: A Logger, Thread, or socket connection that can't meaningfully be serialized. • Sensitive data: Passwords, private keys — you don't want them in the serialized byte stream. Customizing deserialization: Implement readObject() to reinitialize transient fields after deserialization. Note: transient has no effect if you use external serialization frameworks like Jackson or Gson — those use their own exclusion mechanisms (@JsonIgnore, @Expose).

88

What is method hiding in Java?

Method hiding occurs when a subclass defines a static method with the same name and signature as a static method in the superclass. The subclass method hides the parent's static method. Key difference from overriding: The method called depends on the reference type (compile-time), not the object type (runtime). There is no dynamic dispatch for static methods. class Parent { static void greet() { sout("Parent"); } } class Child extends Parent { static void greet() { sout("Child"); } } Parent p = new Child(); p.greet(); // prints "Parent" — static dispatch on reference type Child c = new Child(); c.greet(); // prints "Child" Best practice: Don't override static methods — the hiding semantics are confusing. Access static methods via the class name (Parent.greet()), not through an instance reference.

89

What is the difference between i++ and ++i in Java?

i++ (post-increment): Returns the current value of i, then increments i. The expression evaluates to the original value. int a = 5; int b = a++; // b = 5, a = 6 ++i (pre-increment): Increments i first, then returns the new value. int a = 5; int b = ++a; // b = 6, a = 6 In practice: As standalone statements (i++ vs ++i), there is NO performance difference. The compiler optimizes both to the same bytecode. Difference matters only when the expression value is used: loop initialization, assignments, method arguments. Common mistake: int i = 0; i = i++; // i is still 0! Post-increment returns original value, then that original value is assigned back. For simple increments in loops, either form works. Prefer consistency within your codebase.

90

What are the Java 8 Date/Time APIs and why were they introduced?

Before Java 8: java.util.Date and Calendar were broken — mutable, not thread-safe, confusing API (months are 0-indexed), no timezone support, poor design. Java 8 java.time (JSR-310, inspired by Joda-Time): • LocalDate: Date only (2024-01-15), no timezone • LocalTime: Time only (10:30:00) • LocalDateTime: Date + time, no timezone • ZonedDateTime: Full date + time + timezone (use for user-facing time) • OffsetDateTime: Date + time + UTC offset (use for APIs and storage) • Instant: Machine timestamp (nanoseconds since epoch) • Duration: Time-based amount (between two Instants) • Period: Date-based amount (years, months, days between two LocalDates) • DateTimeFormatter: Thread-safe formatting/parsing Immutable and thread-safe. All operations return new instances. Instant.now(), LocalDate.now(), ZonedDateTime.now(ZoneId.of("Asia/Kolkata"))

91

What is the difference between HashMap.put() and HashMap.putIfAbsent()?

put(key, value): Always inserts the key-value pair. If the key already exists, replaces the old value with the new one. Returns the old value (or null if key was absent). putIfAbsent(key, value): Only inserts if the key is not already present (or mapped to null). Returns the existing value if present, null if key was absent and insertion happened. Concurrent equivalent: ConcurrentHashMap.putIfAbsent() is atomic — safe for race-free initialization in concurrent contexts. Idiom for first-one-wins: map.putIfAbsent("lock", requestId); // only first request sets it Compare with computeIfAbsent(): • putIfAbsent: Value is computed eagerly before the call (even if not used) • computeIfAbsent: Value is computed lazily via a function only if needed — preferred when value computation is expensive

92

What is the difference between String.valueOf() and toString()?

String.valueOf(obj): Static method. Null-safe — returns the string "null" if obj is null. Works for all types (primitives, objects). String.valueOf(null) → "null" String.valueOf(42) → "42" obj.toString(): Instance method. Throws NullPointerException if obj is null. Returns the object's string representation (Object default: ClassName@hashcode). For safe null handling, prefer String.valueOf() or Objects.toString(obj, "default"). String conversion in concatenation: In "Hello " + obj, the compiler calls String.valueOf(obj) which handles null safely. Best practice: Override toString() in your classes to return meaningful output for logging. Include key fields but avoid sensitive data. Lombok @ToString or records auto-generate good toString() implementations.

93

What is an immutable class and how do you create one?

An immutable class's instances cannot be modified after creation. All fields are set in the constructor and never changed. Steps to create an immutable class: 1. Declare class final (prevent subclassing) 2. Declare all fields private and final 3. Initialize all fields in the constructor 4. Provide no setters 5. For mutable field types (Date, List): make defensive copies in constructor AND in getters Example: public final class Point { private final int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } // no setters } Benefits: Thread-safe without synchronization, safe to cache and reuse, safe to use as HashMap keys, no defensive copying needed at usage sites. Java examples: String, Integer, LocalDate, all primitive wrappers. Use Java records for concise immutable data carriers.

94

What is the covariant return type in Java?

Covariant return type allows an overriding method in a subclass to return a more specific (narrower) type than the overridden method in the superclass. Example: class Animal { Animal create() { return new Animal(); } } class Dog extends Animal { @Override Dog create() { return new Dog(); } // Dog is more specific than Animal — valid! } Before Java 5, the return type had to be identical. Covariant return types were introduced in Java 5. This enables fluent builder patterns where each subclass can return itself: class Builder { Builder set(String v) { ...; return this; } } class ConcreteBuilder extends Builder { @Override ConcreteBuilder set(String v) { ...; return this; } // returns own type } ConcreteBuilder cb = new ConcreteBuilder().set("val"); // no cast needed

95

What is the difference between static and instance methods?

Instance methods: Operate on a specific object instance. Can access both static and instance fields. Called via an object reference: obj.method(). This reference is available implicitly. Static methods: Belong to the class, not an instance. Cannot access instance fields or call instance methods directly. No this reference. Called via the class name: ClassName.method(). Available without creating an object. When to use static: Utility/helper methods (Math.sqrt, String.valueOf, Collections.sort), factory methods (LocalDate.of()), methods that don't need object state. When to use instance: Methods that need to read or mutate the object's state. Static fields are shared across all instances — use for constants (static final) or class-level shared state (with care — can cause hidden coupling). Common mistake: Accessing a static method through an instance reference — works but is misleading.

96

What is the difference between Predicate.and(), Predicate.or(), and Predicate.negate()?

These are default methods on the Predicate<T> functional interface that allow composing predicates. Predicate.and(other): Returns a composed predicate that is true only if BOTH this and other are true (logical AND). Short-circuits — if this is false, other is not evaluated. Predicate.or(other): Returns a composed predicate that is true if EITHER this or other is true (logical OR). Short-circuits — if this is true, other is not evaluated. Predicate.negate(): Returns a predicate that is the logical negation of this predicate. Example: Predicate<String> isLong = s -> s.length() > 5; Predicate<String> startsWithA = s -> s.startsWith("A"); Predicate<String> isLongAndStartsWithA = isLong.and(startsWithA); Predicate<String> isShortOrNotA = isLong.negate().or(startsWithA.negate()); list.stream().filter(isLong.and(startsWithA.negate())).toList();

97

What is the Observer pattern and how is it implemented in Java?

The Observer pattern defines a one-to-many dependency: when one object (Subject/Observable) changes state, all dependents (Observers) are notified automatically. Implementation options: 1. java.util.Observable + Observer (deprecated Java 9): Class-based, has design issues, not recommended. 2. Custom implementation: Subject holds a List<Observer>; observers register with addObserver(), removed with removeObserver(); notified via notifyObservers(). 3. PropertyChangeListener: Java Beans standard — add/remove listeners and fire PropertyChangeEvents. 4. Spring ApplicationEvent: @EventListener on a method; ApplicationEventPublisher.publishEvent(). Decoupled, supports async (@Async), works across components. 5. Reactive Streams: Project Reactor (Flux/Mono) or RxJava — Publisher/Subscriber model with backpressure. Modern choice for event-driven systems.

98

What is the Strategy pattern in Java?

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The algorithm can be changed independently of clients that use it. With lambdas: In Java 8+, any functional interface IS a strategy. Pass a lambda where a strategy is expected. Example — sorting: List<String> names = List.of("Charlie", "Alice", "Bob"); // Strategy 1: natural order names.stream().sorted(Comparator.naturalOrder()) // Strategy 2: by length names.stream().sorted(Comparator.comparingInt(String::length)) // Strategy 3: reverse names.stream().sorted(Comparator.reverseOrder()) Comparator<T> is a strategy for comparison. ExecutorService accepts a Runnable/Callable — a strategy for what work to do. Spring's AuthenticationProvider is a strategy for authentication. Benefit: Open/Closed Principle — add new strategies without changing existing code.

99

What is String.intern() and when should you use it?

String.intern() returns a canonical representation from the String Pool. If a string with the same content already exists in the pool, it returns that reference. If not, it adds the string to the pool and returns it. After interning, == comparison works: String a = new String("hello").intern(); String b = new String("hello").intern(); a == b // true — same pool reference Use cases: • When you have millions of repeated strings (e.g., field names, city names) and want to reduce memory by deduplicating them. • When you need == comparison for performance (replaces equals() in hot paths). Downsides: • Adds strings to the pool (a fixed region with limited space in older JVMs). • intern() call itself has overhead (hash lookup in the pool). • Modern alternatives: Java 8 Compact Strings already reduces memory. G1 String Deduplication (-XX:+UseStringDeduplication) deduplicates heap strings automatically without interning.

100

What are the key differences between Java 8, 11, 17, and 21?

Java 8 (LTS): Lambda expressions, Stream API, Optional, Default methods, Date/Time API, CompletableFuture, Nashorn JS engine. Java 11 (LTS): Local-variable syntax for lambda params (var in lambdas), HTTP Client (java.net.http), String methods (strip, isBlank, lines, repeat), Files.readString/writeString, Removal of JavaEE/Corba modules, Flight Recorder open-sourced. Java 17 (LTS): Sealed classes, Pattern matching for instanceof, Records, Text blocks, Switch expressions, RandomGenerator API, Strong encapsulation of JDK internals. Removal of Nashorn, Applet API, SecurityManager. Java 21 (LTS): Virtual Threads (Project Loom) — Production-ready. Pattern matching for switch. Sequenced Collections (SequencedList, SequencedMap). Record patterns. String Templates (preview). Unnamed classes/main (preview). LTS recommendations: Use Java 21 for new projects. Java 17 minimum for Spring Boot 3. Java 11 still widely deployed.

Learn this free with Aria, your AI tutor → AiCanCode.org/learn/interview