Skip to main content

Java Interview Questions & Answers

A curated collection of Java interview questions and answers organized by topic and difficulty level. Questions are drawn from real interview scenarios and cover core Java through expert-level topics.

Difficulty Levels:

  • ๐ŸŸข Intermediate โ€” Solid fundamentals expected
  • ๐ŸŸก Advanced โ€” Deep understanding required
  • ๐Ÿ”ด Expert โ€” Production experience and system design knowledge

1. Core Java & OOPโ€‹

๐ŸŸข What does Java use: pass by value or pass by reference?โ€‹

Java uses pass by value. For primitive types, Java copies the actual value. For objects, Java copies the reference value (the memory address), not the object itself. Changes to the parameter inside a method do not affect the original reference outside the method, but you can modify the object's internal state through the copied reference.

void modify(List<String> list) {
list.add("item"); // Modifies the object โ€” visible to caller
list = new ArrayList<>(); // Reassigns local reference only โ€” NOT visible to caller
}

๐ŸŸข What is the impact of declaring a method as final on inheritance?โ€‹

Declaring a method as final prevents it from being overridden in any subclass. This ensures the method's behavior remains consistent across the class hierarchy. It's commonly used when a specific algorithm or security-sensitive operation must not be altered by subclasses.

๐ŸŸข Can method overloading be determined at runtime?โ€‹

No. Method overloading is resolved at compile-time based on the method signature (name + parameter types). This differs from method overriding, which is resolved at runtime via dynamic dispatch based on the object's actual type.

๐ŸŸข What is a marker interface?โ€‹

A marker interface has no methods or fields. It "marks" a class with a certain capability, enabling instanceof checks at runtime. Examples include Serializable and Cloneable.

// Custom marker interface
public interface Transmittable {}

// Usage: only transmit objects that implement Transmittable
if (data instanceof Transmittable) {
transmit(data);
}

๐ŸŸข Can you modify a final object reference in Java?โ€‹

You cannot reassign a final reference to a different object. However, the object itself can still be mutated if it's mutable:

final List<String> list = new ArrayList<>();
list.add("item"); // โœ… Allowed โ€” mutating the object
list = new ArrayList<>(); // โŒ Compile error โ€” reassigning the reference

๐ŸŸก What are inner classes in Java?โ€‹

Inner classes are classes defined within another class. They have access to the outer class's members (even private ones). Types include:

TypeDescription
Non-static inner classTied to an instance of the outer class
Static nested classNot tied to an outer instance; can have static members
Local classDefined inside a method
Anonymous classUnnamed class defined and instantiated inline

Non-static inner classes cannot contain static declarations, because they are associated with an instance of the outer class. Static nested classes can.

๐ŸŸก What is TypeErasure?โ€‹

Type Erasure is the process by which the Java compiler removes generic type information after compilation. At runtime, List<Integer> and List<String> are both just List. This ensures backward compatibility with pre-generics code but means generic type information is unavailable via reflection.

// At compile time: type-safe
List<String> strings = new ArrayList<>();
// At runtime: type information erased
// Both are just ArrayList

๐ŸŸก Why can't we create an array of generic types in Java?โ€‹

Arrays require concrete type information at runtime to enforce type safety (they perform runtime type checks on insertion). Due to type erasure, generic type information is unavailable at runtime, creating a fundamental incompatibility:

// โŒ Compile error:
// T[] array = new T[10];

// โœ… Workaround using Object array with cast:
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[10];

๐ŸŸก How would generics help maintain type safety and reduce code duplication?โ€‹

Generics allow classes, methods, and collections to use type parameters, catching type mismatches at compile time instead of runtime. A single generic implementation works with multiple types, eliminating the need for type-specific duplicates:

// Without generics: separate methods or unsafe casting
Object item = list.get(0);
String s = (String) item; // ClassCastException risk

// With generics: compile-time safety, no duplication
List<String> list = new ArrayList<>();
String s = list.get(0); // Type-safe, no cast needed

๐Ÿ”ด What happens if a final field is changed using reflection?โ€‹

Reflection can bypass compile-time restrictions and modify a final field using field.setAccessible(true). However, this breaks the immutability contract and can lead to unpredictable behavior because the JIT compiler may inline final field values at compile time. This should be avoided in production code.

๐Ÿ”ด How have records and sealed classes impacted OOP?โ€‹

Records (Java 14+) provide a concise way to model immutable data, auto-generating equals(), hashCode(), and toString(), reinforcing encapsulation and immutability.

Sealed classes (Java 15+) restrict which classes can extend them, giving precise control over inheritance hierarchies and enabling exhaustive pattern matching:

// Record: immutable data carrier
record Point(int x, int y) {}

// Sealed class: controlled hierarchy
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}

2. Collections & Data Structuresโ€‹

๐ŸŸข What are the potential issues with using mutable objects as HashMap keys?โ€‹

If a key object's state changes after insertion, its hashCode() changes, making the entry unreachable in the map โ€” even though it still exists. This causes data loss and potential memory leaks. Always use immutable objects as map keys.

๐ŸŸข What happens if you override only equals() and not hashCode()?โ€‹

The HashMap contract requires that equal objects must have the same hash code. Without consistent hashCode(), the map may store duplicate keys or fail to find existing entries, since it uses hash codes to locate buckets before checking equals().

๐ŸŸข What is the difference between HashMap and IdentityHashMap?โ€‹

AspectHashMapIdentityHashMap
Key comparisonequals() + hashCode() (logical equality)== (reference equality)
Use caseGeneral-purpose mappingIdentity-based operations (e.g., serialization graphs)

๐ŸŸข How does Collections.sort() work internally?โ€‹

It uses TimSort, a modified merge sort that is stable (preserves equal-element order) and optimized for partially sorted data. It breaks the list into small runs, sorts them with insertion sort, and merges them.

๐ŸŸก What causes ConcurrentModificationException and how do you prevent it?โ€‹

It occurs when a collection is structurally modified while being iterated. Prevention strategies:

  1. Use iterator's remove() method during iteration
  2. Use concurrent collections (CopyOnWriteArrayList, ConcurrentHashMap)
  3. Use removeIf() for conditional removal
  4. Collect items to remove in a separate list, then remove after iteration
// โŒ Throws ConcurrentModificationException
for (String s : list) {
if (s.isEmpty()) list.remove(s);
}

// โœ… Safe removal
list.removeIf(String::isEmpty);

๐ŸŸก When would LinkedHashSet outperform TreeSet and vice versa?โ€‹

ScenarioBest ChoiceReason
Frequent insertions/lookupsLinkedHashSetO(1) operations
Insertion order preservationLinkedHashSetMaintains order by design
Sorted element accessTreeSetAuto-sorted, O(log n) operations
Range queries (subSet, headSet)TreeSetNavigable sorted structure

๐ŸŸก How would you implement an LRU cache?โ€‹

Use a LinkedHashMap with access-order enabled, overriding removeEldestEntry():

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;

public LRUCache(int capacity) {
super(capacity, 0.75f, true); // true = access order
this.capacity = capacity;
}

@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}

๐ŸŸก What is the difference between Collections.sort() and Stream.sorted()?โ€‹

AspectCollections.sort()Stream.sorted()
MutabilityMutates the original listReturns a new sorted stream
StyleImperativeFunctional/declarative
ChainingStandalone operationChainable in a pipeline
SourceWorks only on ListWorks on any stream source

๐Ÿ”ด How does ConcurrentHashMap work internally?โ€‹

Java 7: Uses segment-based locking โ€” the map is divided into segments, each with its own lock, allowing concurrent writes to different segments.

Java 8+: Replaced segments with node-level locking using CAS (Compare-And-Swap) operations and synchronized blocks on individual bins. Read operations are generally lock-free using volatile reads. The structure uses an array of nodes, where each bin can be a linked list or a red-black tree (when a bin exceeds 8 entries).


3. Java 8 Featuresโ€‹

๐ŸŸข What is a Functional Interface?โ€‹

An interface with exactly one abstract method. It can have default/static methods. The @FunctionalInterface annotation provides compile-time enforcement:

InterfaceMethodPurpose
Function<T,R>R apply(T)Transform T โ†’ R
Predicate<T>boolean test(T)Filter T โ†’ boolean
Consumer<T>void accept(T)Side-effect T โ†’ void
Supplier<T>T get()Factory () โ†’ T

๐ŸŸข What is the difference between map() and flatMap() in Streams?โ€‹

map() transforms each element 1:1. flatMap() transforms each element into a stream and then flattens all resulting streams into one:

// map: List<String> โ†’ Stream of uppercase strings
list.stream().map(String::toUpperCase);

// flatMap: List<List<String>> โ†’ single Stream<String>
listOfLists.stream().flatMap(Collection::stream);

๐ŸŸข What is the difference between Optional.of() and Optional.ofNullable()?โ€‹

MethodNull handlingUse when
Optional.of(value)Throws NullPointerException if nullValue is guaranteed non-null
Optional.ofNullable(value)Returns Optional.empty() if nullValue might be null

๐ŸŸข What is the difference between findFirst() and findAny() in Streams?โ€‹

findFirst() returns the first element in encounter order โ€” deterministic and useful for sequential streams. findAny() returns any element and is optimized for parallel streams where it can return whichever element is found first across threads.

๐ŸŸก What is the difference between peek() and map()?โ€‹

map() transforms elements and returns a new stream of transformed values. peek() is for side effects (like logging) and returns the same stream unmodified. Caution: peek() behavior is unpredictable for purposes other than debugging, since intermediate operations may not execute if there's no terminal operation.

๐ŸŸก How does Java 8 handle parallel processing with Streams?โ€‹

parallelStream() or .parallel() splits data into chunks processed concurrently via the ForkJoinPool.commonPool(). The pool typically has Runtime.getRuntime().availableProcessors() - 1 threads. The framework handles data splitting, parallel execution, and result merging automatically.

Caution: Parallel streams are not always faster. Overhead from splitting, thread coordination, and merging can outweigh gains for small datasets or simple operations.

๐ŸŸก Can you use this and super in a Lambda expression?โ€‹

Yes, but they refer to the enclosing instance, not the lambda itself (lambdas have no this). this refers to the class where the lambda is defined; super refers to its superclass. This differs from anonymous classes, where this refers to the anonymous class instance.

๐ŸŸก What happens if you modify a local variable inside a Lambda?โ€‹

It causes a compile-time error. Local variables accessed from within a lambda must be final or effectively final (not modified after initialization). This ensures thread safety and prevents side effects in functional-style code.

๐ŸŸก How do Default Methods in interfaces affect design decisions vs abstract classes?โ€‹

Default methods blur the line between interfaces and abstract classes by allowing interfaces to provide implementations. Key differences remain:

FeatureInterface (with defaults)Abstract Class
Multiple inheritanceโœ… A class can implement manyโŒ Single inheritance
State (fields)โŒ Only constantsโœ… Instance fields
ConstructorsโŒ Noneโœ… Supported
Access modifierspublic only (until Java 9)Any access level

Choose interfaces for shared behavior across unrelated types. Choose abstract classes when shared state or a common base is needed.

๐ŸŸก Can a Lambda throw an exception?โ€‹

Yes. Unchecked exceptions can be thrown freely. Checked exceptions must either be caught within the lambda or the functional interface must declare them. Since standard functional interfaces (e.g., Function, Predicate) don't declare checked exceptions, you need a try-catch inside the lambda or a custom functional interface.


4. Concurrency & Multithreadingโ€‹

๐ŸŸข How would you ensure safe access to a shared resource by multiple threads?โ€‹

Use synchronization mechanisms:

  1. synchronized keyword on methods or blocks
  2. ReentrantLock for advanced locking with fairness and timeouts
  3. Atomic classes (AtomicInteger, AtomicReference) for lock-free thread safety
  4. Concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList)

๐ŸŸข What is the significance of volatile in Java concurrency?โ€‹

The volatile keyword ensures that reads and writes to a variable go directly to main memory, bypassing CPU caches. This guarantees visibility โ€” changes by one thread are immediately visible to others. However, volatile does not provide atomicity for compound operations (e.g., count++).

๐ŸŸข Can volatile replace synchronized?โ€‹

No. volatile ensures visibility but not mutual exclusion. For compound operations (check-then-act, read-modify-write), you still need synchronized or Lock:

// โŒ Not thread-safe even with volatile
volatile int count = 0;
count++; // This is read + increment + write (3 operations)

// โœ… Thread-safe alternatives
synchronized(this) { count++; }
// or
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();

๐ŸŸข What are the differences between Runnable and Callable?โ€‹

FeatureRunnableCallable<V>
Methodvoid run()V call()
Return valueNoneReturns a result
Checked exceptionsCannot throwCan throw
Usage with Executorexecute() or submit()submit() only

๐ŸŸก What is the difference between synchronized and ReentrantLock?โ€‹

FeaturesynchronizedReentrantLock
Lock acquisitionImplicit (enter block)Explicit (lock() / unlock())
FairnessNo controlConfigurable fair/unfair
Try-lockNot possibletryLock(timeout) supported
InterruptibleNolockInterruptibly() supported
Multiple conditionsOne wait-set per monitorMultiple Condition objects
Automatic releaseYes (on block exit/exception)Manual (must call unlock() in finally)

๐ŸŸก Can a deadlock occur with a single thread?โ€‹

A single thread can experience a self-deadlock if it tries to recursively acquire a non-reentrant lock it already holds. This is rare and typically a programming error. ReentrantLock and synchronized (which is reentrant) prevent this by design.

๐ŸŸก What is the difference between synchronized and concurrent collections?โ€‹

Synchronized collections (e.g., Collections.synchronizedList()) wrap standard collections with a single lock โ€” only one thread accesses at a time. Concurrent collections (e.g., ConcurrentHashMap, CopyOnWriteArrayList) use fine-grained locking or lock-free algorithms, allowing higher throughput under contention.

๐ŸŸก What is CountDownLatch vs CyclicBarrier?โ€‹

FeatureCountDownLatchCyclicBarrier
ReusableโŒ One-time useโœ… Can be reset
DirectionN threads count down, 1+ threads waitN threads wait for each other
Action on completionNone built-inOptional barrier action
Use caseWait for services to initializeMulti-phase computation

๐ŸŸก How does the Executor Framework handle task interruption?โ€‹

Tasks check for interruption via Thread.interrupted() or isInterrupted(). Best practices:

  1. Regularly check interruption status in long-running tasks
  2. Catch InterruptedException and clean up resources
  3. Use Future.cancel(true) to interrupt running tasks
  4. Restore the interrupt flag if catching InterruptedException without terminating

๐ŸŸก What is RejectedExecutionHandler in ThreadPoolExecutor?โ€‹

Handles tasks that cannot be executed when the pool and queue are full. Built-in policies:

PolicyBehavior
AbortPolicy (default)Throws RejectedExecutionException
CallerRunsPolicyExecutes task in the submitting thread
DiscardPolicySilently discards the task
DiscardOldestPolicyDiscards oldest queued task, retries submission

๐Ÿ”ด Explain the difference between visibility and atomicity in multithreading.โ€‹

Visibility: Whether changes made by one thread are seen by other threads. Solved by volatile, synchronized, or memory barriers.

Atomicity: Whether an operation completes as an indivisible unit. A volatile long read/write is atomic on 64-bit, but count++ on a volatile int is NOT atomic (it's read + modify + write). Atomic classes or locks are needed for compound operations.

๐Ÿ”ด Explain the internal working of ThreadPoolExecutor.โ€‹

  1. Task submitted โ†’ If active threads < corePoolSize, create a new worker thread
  2. Core full โ†’ Place task in the workQueue (e.g., LinkedBlockingQueue)
  3. Queue full โ†’ If active threads < maximumPoolSize, create a new thread
  4. Max reached + queue full โ†’ Invoke RejectedExecutionHandler
  5. Idle threads exceeding corePoolSize are terminated after keepAliveTime

States: RUNNING โ†’ SHUTDOWN (no new tasks, complete existing) โ†’ STOP (interrupt all) โ†’ TIDYING โ†’ TERMINATED

๐Ÿ”ด How many threads does a parallel stream use?โ€‹

Parallel streams default to the ForkJoinPool.commonPool(), which has availableProcessors() - 1 threads. You can customize this:

// Custom pool with specific parallelism
ForkJoinPool customPool = new ForkJoinPool(8);
customPool.submit(() ->
list.parallelStream()
.filter(...)
.collect(Collectors.toList())
).get();

๐Ÿ”ด Write the Producer/Consumer problem using wait/notify.โ€‹

class ProducerConsumer {
private final LinkedList<Integer> buffer = new LinkedList<>();
private final int CAPACITY = 5;
private int value = 0;

public void produce() throws InterruptedException {
while (true) {
synchronized (this) {
while (buffer.size() == CAPACITY) {
wait(); // Release lock and wait for space
}
System.out.println("Produced: " + value);
buffer.add(value++);
notify(); // Notify consumer
}
}
}

public void consume() throws InterruptedException {
while (true) {
synchronized (this) {
while (buffer.isEmpty()) {
wait(); // Release lock and wait for items
}
int consumed = buffer.removeFirst();
System.out.println("Consumed: " + consumed);
notify(); // Notify producer
}
}
}
}

Key points:

  • while loop (not if) for wait condition โ€” guards against spurious wakeups
  • wait() releases the monitor lock and suspends the thread
  • notify() wakes one waiting thread; notifyAll() wakes all

5. Memory Management & JVMโ€‹

๐ŸŸข How does Java handle memory leaks?โ€‹

Java's garbage collector automatically reclaims unreachable objects. However, memory leaks still occur when objects are unintentionally retained:

  • Static collections holding references indefinitely
  • Unclosed resources (streams, connections)
  • Listener/callback accumulation
  • Inner classes holding outer class references
  • ThreadLocal variables not cleaned up

๐ŸŸข What is the difference between NoClassDefFoundError and ClassNotFoundException?โ€‹

ErrorWhenCause
ClassNotFoundExceptionRuntime (Class.forName, ClassLoader)Class not on classpath
NoClassDefFoundErrorRuntimeClass was available at compile time but not at runtime (e.g., static initializer failure)

๐ŸŸข How does the static keyword affect memory management?โ€‹

Static fields and methods are stored in the Method Area/Metaspace (not per-instance heap memory). They are created when the class is loaded and persist as long as the class is loaded, shared among all instances. This can inadvertently cause memory leaks if static collections grow unboundedly.

๐ŸŸก What is Metaspace and how does it differ from PermGen?โ€‹

AspectPermGen (โ‰ค Java 7)Metaspace (Java 8+)
LocationJVM heapNative memory
SizingFixed (-XX:MaxPermSize)Dynamic (grows as needed)
OOM riskFrequent (OutOfMemoryError: PermGen)Rare (uses native memory)
ContentClass metadata, string pool, static varsClass metadata only

๐ŸŸก What are Strong, Weak, Soft, and Phantom References?โ€‹

TypeGC BehaviorUse Case
StrongNever collected while reachableNormal object references
SoftCollected only when JVM is low on memoryMemory-sensitive caches
WeakCollected at next GC cycleWeakHashMap, canonicalizing maps
PhantomEnqueued after finalization, before memory reclaimResource cleanup tracking

๐ŸŸก How does garbage collection handle circular references?โ€‹

Java's GC uses reachability analysis from GC roots (stack frames, static fields, JNI references), not reference counting. Objects in a circular reference are collected if none of them are reachable from any GC root โ€” the circular references don't prevent collection.

๐ŸŸก What is the difference between Class.forName() and ClassLoader.loadClass()?โ€‹

MethodInitializationUse when
Class.forName()Loads AND initializes (runs static blocks)Class needs immediate initialization
ClassLoader.loadClass()Loads but does NOT initializeDeferring initialization for performance

๐Ÿ”ด How would you investigate an OutOfMemoryError?โ€‹

  1. Check JVM settings: -Xms, -Xmx heap size configuration
  2. Capture heap dump: -XX:+HeapDumpOnOutOfMemoryError or jmap -dump:live,format=b
  3. Analyze with tools: Eclipse MAT, VisualVM, JProfiler
  4. Review code: Look for unbounded caches, unclosed resources, large collections
  5. Monitor runtime: Use JConsole/VisualVM to track heap usage patterns over time
  6. Check Metaspace: If the error mentions Metaspace, investigate class loading leaks

๐Ÿ”ด Explain all garbage collectors up to the latest Java release.โ€‹

CollectorThreadsPauseBest For
Serial GCSingleStop-the-worldSmall apps, single-core
Parallel GCMultiStop-the-worldThroughput-focused batch jobs
CMSConcurrent mark + sweepShort pausesLegacy responsive apps (deprecated)
G1 GCConcurrent + parallelPredictable pausesGeneral purpose (default Java 11+)
ZGCConcurrentUltra-low (< 1ms)Large heaps, latency-critical
ShenandoahConcurrentLow-latencyLarge heaps, similar to ZGC

Defaults by version:

  • Java 8โ€“10: Parallel GC
  • Java 11+: G1 GC
  • ZGC and Shenandoah available from Java 15+

๐Ÿ”ด How would you structure code to avoid memory leaks in long-running applications?โ€‹

  1. Close resources with try-with-resources
  2. Use weak references for cache objects (WeakHashMap, SoftReference)
  3. Avoid static references to large or growing collections
  4. Unregister listeners/callbacks when no longer needed
  5. Clean up ThreadLocal variables (call remove())
  6. Use connection pooling for database/network resources
  7. Profile regularly with VisualVM or Java Flight Recorder

๐Ÿ”ด How do you create a high-performance system with minimal GC?โ€‹

  • Reduce object creation: prefer primitives over wrapper types
  • Object pooling for frequently created/destroyed objects
  • Reuse collections with clear() instead of re-allocating
  • Use off-heap storage for large datasets (ByteBuffer.allocateDirect())
  • Choose the right GC: ZGC or Shenandoah for low-latency
  • Tune JVM: -Xms = -Xmx to avoid heap resizing, appropriate young/old gen ratios

6. Design Patterns & Best Practicesโ€‹

๐ŸŸข What is the Builder Pattern and how does it differ from Factory?โ€‹

AspectBuilder PatternFactory Pattern
PurposeConstruct complex objects step by stepCreate objects in a single step
ControlFine-grained control over constructionHides creation logic from client
Use caseMany optional parametersChoosing between related types
// Builder pattern
User user = User.builder()
.name("Alice")
.age(30)
.build();

๐ŸŸก What is the difference between Strategy and State patterns?โ€‹

Both use composition and polymorphism, but serve different purposes:

AspectStrategyState
PurposeSelect an algorithm at runtimeChange behavior based on internal state
TriggerExternal (client chooses strategy)Internal (state transitions)
AwarenessStrategies are independentStates know about transitions

๐ŸŸก How would you apply the Observer pattern in an event-driven application?โ€‹

Observers (listeners) register with a subject (event source). When the subject triggers an event, all registered observers are notified and react accordingly. This decouples event sources from response logic:

// Subject
interface EventPublisher {
void subscribe(EventListener listener);
void unsubscribe(EventListener listener);
void publish(Event event);
}

// Observer
interface EventListener {
void onEvent(Event event);
}

๐ŸŸก How can you break a Singleton? How do you prevent it?โ€‹

Attack VectorPrevention
ReflectionThrow exception in constructor if instance exists
SerializationImplement readResolve() returning the singleton
CloningOverride clone() to throw CloneNotSupportedException
Multiple classloadersUse enum-based singleton

Best approach: Use an enum singleton, which prevents all of the above by design:

public enum ConfigManager {
INSTANCE;

public String getConfig(String key) { /* ... */ }
}

๐ŸŸก Implement a thread-safe Singleton without synchronized.โ€‹

Bill Pugh Singleton โ€” leverages the classloader mechanism for lazy, thread-safe initialization:

public class Singleton {
private Singleton() {}

private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return Holder.INSTANCE;
}
}

The inner class Holder is loaded only when getInstance() is first called, and the JVM guarantees class loading is thread-safe.

๐Ÿ”ด How would you implement Singleton and Strategy patterns using enums?โ€‹

// Singleton via enum
public enum AppConfig {
INSTANCE;
private final Properties props = new Properties();
public String get(String key) { return props.getProperty(key); }
}

// Strategy via enum
public enum SortStrategy {
QUICKSORT {
@Override public <T> void sort(List<T> list, Comparator<T> c) { /* quicksort impl */ }
},
MERGESORT {
@Override public <T> void sort(List<T> list, Comparator<T> c) { /* mergesort impl */ }
};

public abstract <T> void sort(List<T> list, Comparator<T> c);
}

// Usage: SortStrategy.QUICKSORT.sort(myList, comparator);

7. Serialization & Class Loadingโ€‹

๐ŸŸข Can you serialize static fields in Java?โ€‹

No. Serialization captures the object's instance state. Static fields belong to the class, not individual objects, and are excluded from serialization.

๐ŸŸข What happens if a Serializable class contains a non-serializable member?โ€‹

A NotSerializableException is thrown during serialization. Solutions:

  1. Mark the field as transient (excluded from serialization)
  2. Make the member class implement Serializable
  3. Provide custom writeObject()/readObject() methods

๐ŸŸก What are the differences between Externalizable and Serializable?โ€‹

FeatureSerializableExternalizable
Implementation effortNone (marker interface)Must implement writeExternal()/readExternal()
ControlDefault mechanismComplete control over serialization
PerformanceCan be slower (serializes everything)Can be faster (selective serialization)
transient fieldsSupportedNot needed (you control what's written)

๐ŸŸก What are the different types of class loaders in Java?โ€‹

  1. Bootstrap ClassLoader โ€” Loads core Java classes (java.lang, java.util from rt.jar)
  2. Extension/Platform ClassLoader โ€” Loads extension classes from lib/ext
  3. Application/System ClassLoader โ€” Loads classes from application classpath

They follow the parent delegation model: each loader delegates to its parent first, only loading the class itself if the parent cannot.

๐Ÿ”ด What are dynamic proxies in Java?โ€‹

Dynamic proxies create proxy instances for interfaces at runtime, without explicit class definitions. They intercept method calls via an InvocationHandler:

interface UserService {
User findById(long id);
}

UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
(proxyObj, method, args) -> {
System.out.println("Calling: " + method.getName());
// Delegate to real implementation, add logging/caching/etc.
return realService.findById((long) args[0]);
}
);

Used extensively in frameworks for AOP, transaction management, and lazy loading.

๐Ÿ”ด What is a hidden class (Java 15+)?โ€‹

A hidden class is a non-discoverable, dynamically created class that cannot be found by name or via reflection. It's used by frameworks for runtime-generated classes (like lambda expressions and proxy classes). Benefits:

  • Cannot pollute the application classpath
  • Reduces classloader memory leaks
  • Can be unloaded independently when no longer needed

8. Exception Handlingโ€‹

๐ŸŸข What happens when an exception is thrown in a static initialization block?โ€‹

It wraps the exception in ExceptionInInitializerError, and subsequent attempts to use the class throw NoClassDefFoundError because the class failed to initialize.

๐ŸŸข When would you use a checked exception over an unchecked one?โ€‹

Use checked exceptions for recoverable conditions that the caller should handle (e.g., IOException, SQLException). Use unchecked exceptions for programming errors (e.g., NullPointerException, IllegalArgumentException).

๐ŸŸก Why is catching Throwable considered bad practice?โ€‹

Throwable is the superclass of both Exception and Error. Catching it intercepts JVM-level errors like OutOfMemoryError and StackOverflowError, which typically indicate unrecoverable conditions. Handling these errors can mask critical problems and lead to system instability.

๐ŸŸก What unexpected behavior can the finally block cause?โ€‹

If a new exception is thrown in finally, it replaces the original exception from the try block, causing it to be lost:

try {
throw new RuntimeException("Original");
} finally {
throw new RuntimeException("From finally"); // Original exception is lost!
}
// Only "From finally" propagates

Best practice: Use try-with-resources instead of manual finally blocks for resource cleanup.


9. Modern Java Features (Java 9+)โ€‹

๐ŸŸก How does the module system (Java 9) impact application architecture?โ€‹

The Java Platform Module System (JPMS) enables:

  • Explicit dependencies via module-info.java
  • Strong encapsulation โ€” internal packages are hidden by default
  • Reduced memory footprint โ€” load only required modules
  • Improved security โ€” no access to unexported internals
module com.myapp.core {
requires java.sql;
exports com.myapp.core.api; // Public API
// com.myapp.core.internal is hidden
}

๐ŸŸก What is the purpose of @Retention and @Target annotations?โ€‹

@Retention controls how long an annotation is available:

ValueAvailable at
SOURCESource code only (discarded by compiler)
CLASSCompiled bytecode (not available via reflection)
RUNTIMERuntime (accessible via reflection)

@Target restricts where an annotation can be applied: METHOD, FIELD, TYPE, CONSTRUCTOR, PARAMETER, etc.

๐ŸŸก What is a record in Java (14+)?โ€‹

A concise declaration for immutable data carriers that auto-generates equals(), hashCode(), toString(), and accessor methods:

record Point(int x, int y) {}

// Equivalent to a class with:
// - final fields x, y
// - Constructor Point(int x, int y)
// - Accessors x(), y()
// - equals(), hashCode(), toString()

๐ŸŸก What is a sealed class (Java 15+)?โ€‹

A sealed class restricts which classes can extend it, enabling exhaustive type hierarchies:

sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
final class Triangle implements Shape { /* ... */ }

Benefits: Type safety, exhaustive pattern matching, controlled extensibility.

๐Ÿ”ด How does the Java Reflection API work, and what are its use cases?โ€‹

Reflection allows runtime inspection and modification of classes, methods, and fields. The API can:

  • Create instances dynamically
  • Invoke methods by name
  • Access and modify private fields
  • Inspect annotations

Use cases: Dependency injection (Spring), ORM mapping (Hibernate), test frameworks (JUnit), serialization

Caution: Reflection bypasses access control, has performance overhead, and can break encapsulation. Use sparingly and prefer compile-time alternatives when possible.


10. Performance & Troubleshootingโ€‹

๐ŸŸก What tools and techniques identify memory leaks?โ€‹

ToolPurpose
VisualVMReal-time heap monitoring, thread analysis
Eclipse MATHeap dump analysis, dominator tree, leak suspects
JProfiler / YourKitCPU + memory profiling
Java Flight RecorderLow-overhead production profiling
jmapHeap dump generation
jstatGC statistics monitoring

๐ŸŸก What are the disadvantages of JIT compilation?โ€‹

  • Higher memory usage during compilation of bytecode to native code
  • Increased CPU load during initial execution (warm-up phase)
  • Startup overhead โ€” short-lived applications may terminate before JIT benefits kick in

Consider disabling JIT (-Djava.compiler=NONE) for development/debugging or use AOT compilation (GraalVM Native Image) for fast-startup scenarios.

๐Ÿ”ด How would you improve scalability and memory efficiency of a large Java application?โ€‹

  1. Efficient data structures โ€” choose the right collection for access patterns
  2. Object pooling โ€” reuse expensive objects (connections, threads)
  3. Caching โ€” Redis/Caffeine for frequently accessed data
  4. Lazy initialization โ€” create objects only when needed
  5. Connection pooling โ€” HikariCP for database connections
  6. JVM tuning โ€” heap size, GC selection, Metaspace config
  7. Horizontal scaling โ€” distribute load across instances
  8. Remove unnecessary references โ€” prevent memory leaks
  9. Profile continuously โ€” identify hotspots with JFR/async-profiler

๐Ÿ”ด What performance optimizations have you applied in Java projects?โ€‹

Common optimization strategies:

  • Caching (Redis, Caffeine) to reduce database calls
  • Query optimization โ€” proper indexing, batch operations
  • Appropriate data structures โ€” ConcurrentHashMap over synchronized HashMap
  • Connection pooling (HikariCP) for database access
  • JVM tuning โ€” GC selection, heap sizing
  • Lazy loading โ€” defer expensive initialization
  • Async processing โ€” CompletableFuture for non-blocking operations

11. Senior Expert Questionsโ€‹

๐Ÿ”ด Explain how Stream pipelines execute lazily. What does "element-by-element" mean?โ€‹

Stream operations are not executed when chained โ€” only when a terminal operation triggers them. When triggered, each element passes through the entire pipeline before the next element is processed:

// Interleaved execution order (NOT filter-all then map-all):
// filter: Alice โ†’ map: Alice โ†’ filter: Bob (filtered out) โ†’ filter: Charlie โ†’ map: Charlie
names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());

This enables short-circuiting: findFirst() stops processing as soon as a match is found โ€” the remaining elements are never evaluated.

๐Ÿ”ด What are the traps when using parallel streams?โ€‹

// Trap 1: Non-thread-safe accumulation
List<String> result = new ArrayList<>();
list.parallelStream().forEach(result::add); // โŒ Race condition on ArrayList

// Trap 2: forEach ordering is not guaranteed
list.parallelStream().forEach(System.out::println); // โŒ Random order

// Trap 3: Blocking operations in ForkJoinPool.commonPool()
list.parallelStream().map(this::callDatabase).collect(...); // โŒ Starves commonPool

// โœ… Correct: use collectors (thread-safe merge) and custom pool
ForkJoinPool pool = new ForkJoinPool(4);
pool.submit(() -> list.parallelStream().map(this::heavyTransform).collect(Collectors.toList())).get();

LinkedList and TreeMap split poorly for parallel streams because they lack the SIZED Spliterator characteristic โ€” prefer ArrayList, arrays, or IntStream.range().

๐Ÿ”ด How does StructuredTaskScope fix the problems with CompletableFuture.allOf()?โ€‹

CompletableFuture.allOf() has two fundamental problems:

  1. If one future fails, the others keep running and consuming resources โ€” no automatic cancellation
  2. Threads can outlive their logical scope, making error tracking and cancellation complex

StructuredTaskScope enforces that all subtasks finish (normally or cancelled) before the scope exits:

// With allOf: both fetches run even if one fails
CompletableFuture.allOf(fetchUser(), fetchOrders()).join();

// With StructuredTaskScope: failure cancels all remaining work
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var user = scope.fork(this::fetchUser);
var orders = scope.fork(this::fetchOrders);
scope.join().throwIfFailed();
return new Dashboard(user.get(), orders.get());
} // cancelled subtasks guaranteed to finish here

๐Ÿ”ด Why does JIT deoptimization cause latency spikes in production?โ€‹

JIT compiles methods based on observed runtime behavior. If it assumes a virtual method is monomorphic (one concrete type) and inlines it aggressively โ€” then a new type appears โ€” it must deoptimize: throw away the compiled version and fall back to interpreter.

This can happen when:

  • A rarely-used code path introduces a new type for an inlined call site
  • A cache miss causes previously-cold code paths to execute
  • Kubernetes pods receive production traffic before JVM warm-up completes

Detection: -XX:+PrintCompilation shows deoptimization events. Async-profiler can pinpoint which methods deoptimize frequently.

๐Ÿ”ด What is a G1 "humongous" object and why is it a GC problem?โ€‹

Objects larger than 50% of a G1 region (default region = 1โ€“32 MB) are humongous objects. They:

  1. Are allocated directly in Old gen (skipping Young gen)
  2. Occupy multiple contiguous regions
  3. Are only collected during a full GC (or when the region becomes empty)

Frequent large object allocation (e.g., large JSON payloads, big byte arrays) can fill Old gen with humongous regions that GC cannot efficiently reclaim, causing eventual Full GC stop-the-world pauses.

Fix: Increase region size (-XX:G1HeapRegionSize=32m) or reduce object sizes via streaming/chunking.

๐Ÿ”ด When would you use ScopedValue over ThreadLocal?โ€‹

ThreadLocal pitfalls in virtual thread / structured concurrency world:

  • Thread pool reuse: a virtual thread might be rescheduled on a different carrier thread after an I/O yield, leaving stale ThreadLocal values
  • Memory leaks: forgetting to call remove() in large thread pools
  • No bounded lifetime: values persist for the thread's entire lifecycle

ScopedValue advantages:

// ThreadLocal: survives beyond its logical scope
static final ThreadLocal<User> USER = new ThreadLocal<>();
USER.set(authenticatedUser);
asyncTask(); // forked task inherits stale user โ€” dangerous

// ScopedValue: bounded, immutable, safe with virtual threads
static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
ScopedValue.where(CURRENT_USER, user).run(() -> {
asyncTask(); // only accessible within this scope
});

Use ScopedValue for request-scoped context (auth tokens, trace IDs) in Java 21+ applications using virtual threads.

๐Ÿ”ด Why does HashMap use power-of-2 capacity and a 0.75 load factor?โ€‹

Power-of-2 capacity: Enables index = hash & (capacity - 1) โ€” a fast bitwise AND instead of slow modulo division. When the capacity is a power of 2, capacity - 1 is a bitmask of all 1s in the lower bits.

Load factor 0.75: Empirically balances:

  • Too low (e.g., 0.5): Wastes memory, more rehashing, better lookup performance
  • Too high (e.g., 0.9): Less memory waste, but longer chains โ†’ worse lookup O(n) performance

At 0.75, a map of capacity 16 rehashes at 12 entries, giving a good mix of time and space efficiency.

๐Ÿ”ด How can a custom ClassLoader cause a ClassCastException at runtime even when types look identical?โ€‹

Each ClassLoader has its own namespace. If ClassA is loaded by two different ClassLoaders, the JVM treats them as two different classes โ€” even if the bytecode is identical.

ClassLoader1.loadClass("com.example.User") โ†’ User (version A)
ClassLoader2.loadClass("com.example.User") โ†’ User (version B)
// These are NOT the same class to the JVM!
User u = (User) obj; // ClassCastException if obj was loaded by different loader

This is the root cause of many ClassCastException bugs in Tomcat, OSGi, and hot-reload frameworks where multiple ClassLoaders exist. The fix is ensuring both sides of a cast use the same ClassLoader.

๐Ÿ”ด What is the difference between thenApply, thenCompose, and handle in CompletableFuture?โ€‹

MethodWhen it runsTransform typeUse case
thenApply(f)On successT โ†’ U (synchronous)Simple value transform
thenCompose(f)On successT โ†’ CompletableFuture<U>Chain another async call
handle(f)Always (success + failure)(T, Throwable) โ†’ URecovery + transform in one
exceptionally(f)On failure onlyThrowable โ†’ TError fallback value
whenComplete(f)Always(T, Throwable) โ†’ voidSide-effect only (logging)

thenCompose vs thenApply is the most common interview trap:

// thenApply wraps: returns CompletableFuture<CompletableFuture<Order>>
CompletableFuture<CompletableFuture<Order>> bad = userFuture.thenApply(u -> fetchOrders(u));

// thenCompose flattens: returns CompletableFuture<Order> โ€” correct
CompletableFuture<Order> good = userFuture.thenCompose(u -> fetchOrders(u));

๐Ÿ”ด How would you write a custom Collector for grouping and counting in a single pass?โ€‹

// Built-in: two passes (one to group, one to count)
Map<String, Long> countByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));

// Custom Collector for advanced aggregation:
Collector<Person, Map<String, long[]>, Map<String, Double>> averageSalaryByCity =
Collector.of(
HashMap::new, // supplier
(map, p) -> map.computeIfAbsent( // accumulator
p.getCity(), k -> new long[]{0L, 0L})
.let(a -> { a[0] += p.getSalary(); a[1]++; }),
(map1, map2) -> { // combiner (parallel)
map2.forEach((k, v) -> map1.merge(k, v,
(a, b) -> new long[]{a[0]+b[0], a[1]+b[1]}));
return map1;
},
map -> map.entrySet().stream() // finisher
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> (double) e.getValue()[0] / e.getValue()[1]))
);

For senior interviews, show awareness of the four components: supplier (mutable container), accumulator (add element), combiner (merge parallel results), finisher (transform to final result).

๐Ÿ”ด What is the "Happens-Before" relationship in the Java Memory Model, and why is it important?โ€‹

Happens-Before is a formal memory visibility contract. If action A happens-before action B, the memory writes made by A are guaranteed to be visible to the thread performing B, and the JIT compiler/CPU is prohibited from reordering these operations.

Without a happens-before relationship, the JVM can reorder instructions or cache variables in CPU registers indefinitely, leading to stale reads and data corruption. Key triggers for happens-before include:

  • Releasing a monitor lock (unlock) happens-before subsequently acquiring that same lock (lock).
  • A write to a volatile field happens-before any subsequent read of that field.
  • Calling Thread.start() happens-before any action in the started thread.

๐Ÿ”ด How does StampedLock optimize performance compared to ReentrantReadWriteLock?โ€‹

StampedLock (Java 8+) introduces Optimistic Reading. In standard read-write locks, acquiring a read lock blocks write operations. Under heavy read contention, writers can suffer from starvation.

StampedLock solves this by allowing non-blocking reads:

  • It returns a numeric stamp representing the lock's state without acquiring a read lock.
  • The thread reads the fields into local variables.
  • It then calls lock.validate(stamp) to check if a write lock was acquired concurrently.
  • If validation succeeds, the read data is safe. If it fails, the thread falls back to a blocking read lock.

Optimistic reads are completely lock-free, avoiding CAS operations and memory barriers, which drastically increases scalability in high-read, low-write backend services.

๐Ÿ”ด Why does ThreadLocal cause memory leaks in web servers, and how do you prevent it?โ€‹

Web application containers (like Tomcat, Spring Boot) use a pool of worker threads that are reused across HTTP requests.

Each Thread contains a ThreadLocalMap where keys are weak references to the ThreadLocal object, but the values are strong references.

  1. When a request ends, the ThreadLocal reference in the stack frame is discarded and cleaned by GC.
  2. The key in the map becomes null (since it is weakly referenced).
  3. However, because the thread is returned to the pool and remains alive, the value object remains strongly reachable via the thread's map, causing a memory leak.

Prevention: Always call ThreadLocal.remove() in a finally block before the request execution completes.

๐Ÿ”ด Explain the internal mechanism of WeakHashMap and its caching use cases.โ€‹

WeakHashMap stores its keys wrapped in WeakReference objects.

  • When a key has no other strong references in the application, the garbage collector clears it.
  • During garbage collection, the cleared reference is put into a ReferenceQueue.
  • On subsequent operations on the map (like get(), put(), or size()), WeakHashMap polls the queue and removes the corresponding entries (stale values) from its internal table.

It is useful for memory-sensitive caching (e.g., metadata maps associated with dynamic classloaders or database connection drivers) where you want cached entries to be collected automatically when the owner object is discarded.

๐Ÿ”ด How would you troubleshoot a thread deadlock or a CPU spike in a running production JVM?โ€‹

  • For Deadlocks: Generate a thread dump using jcmd <pid> Thread.print or jstack <pid>. The JVM automatically identifies deadlocked threads and prints their stack traces, indicating which lock they hold and which lock they are waiting for.
  • For CPU Spikes:
    1. Identify the high-CPU native thread ID (TID) using top -H -p <pid>.
    2. Convert the decimal TID to hexadecimal (e.g., 12345 \rightarrow 0x3039).
    3. Capture a thread dump using jstack <pid>.
    4. Search the dump for nid=0x3039 to find the exact thread name, state, and line of code causing the CPU utilization.

๐Ÿ”ด How did Java 9+ optimize String concatenation under the hood?โ€‹

Prior to Java 9, string concatenation (e.g., "a" + "b" + "c") compiled to nested StringBuilder.append() calls. This hardcoded the optimization strategy in compiled bytecode.

Since Java 9, the compiler emits an invokedynamic call pointing to StringConcatFactory.

  • The bootstrap method resolves the concatenation strategy dynamically at runtime (e.g., allocating a single byte array and copying elements directly).
  • This decouples compiling from runtime optimizations: the JVM can improve string allocation strategies (reducing memory allocations by up to 10% in hot paths) without requiring developers to recompile their classes.

Advanced Editorial Pass: Interview Mastery with System Thinkingโ€‹

What Differentiates Senior Answersโ€‹

  • Links language knowledge to system reliability, scalability, and maintainability.
  • Uses trade-offs and measurable outcomes, not only definitions.
  • Shows clear reasoning under uncertainty and failure scenarios.

Preparation Pitfallsโ€‹

  • Relying on memorized one-liners without implementation context.
  • Ignoring performance diagnostics and observability dimensions.
  • Weak examples that do not show decision quality.

Practice Strategyโ€‹

  1. Reframe each answer around context, decision, trade-off, and result.
  2. Add one failure case and one prevention strategy per question.
  3. Practice whiteboard explanations that include data flow and bottlenecks.

Compare Nextโ€‹