0% found this document useful (0 votes)
4 views52 pages

Advanced Java Interview Mastery Handbook-C

The Advanced Java Interview Mastery Handbook provides in-depth preparation for technical interviews at product-based companies, covering essential topics such as time and space complexity, data structures, algorithms, and Java internals. It emphasizes the importance of understanding performance trade-offs, Big-O analysis, and the internal workings of Java collections like HashMap and Trie. The handbook includes tricky interview questions and detailed explanations to help candidates demonstrate senior-level thinking in their responses.

Uploaded by

kavibhagya30
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views52 pages

Advanced Java Interview Mastery Handbook-C

The Advanced Java Interview Mastery Handbook provides in-depth preparation for technical interviews at product-based companies, covering essential topics such as time and space complexity, data structures, algorithms, and Java internals. It emphasizes the importance of understanding performance trade-offs, Big-O analysis, and the internal workings of Java collections like HashMap and Trie. The handbook includes tricky interview questions and detailed explanations to help candidates demonstrate senior-level thinking in their responses.

Uploaded by

kavibhagya30
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

ADVANCED JAVA

Interview Mastery Handbook


─────────────────────────────────────────

Deep Technical Preparation for Product-Based Companies

Covering: JVM Internals • Concurrency • Data Structures • Design Patterns


System Design • Algorithms • OOP Mastery • Java Collections

Advanced Edition — Tricky Questions Included

Advanced Java Interview Mastery Handbook


CHAPTER 1: Time & Space Complexity — The
Interviewer's First Weapon
Interviewers don't just want to know if you know Big-O. They want to see if you can reason through
edge cases, amortized analysis, and make trade-off decisions under constraints. This chapter goes
beyond the basics.

1.1 Big-O: Beyond the Textbook


Most candidates know O(n log n) for sorting. The real test is whether you can identify the exact
constant factors, understand when Big-O hides practical performance, and handle tricky nested loop
patterns.

🎯 Tricky Interview Question


What is the time complexity of this code? for(int i=0; i<n; i++) for(int j=i; j<n; j++)
[Link](i+j); Most say O(n^2) instantly. That's correct—but can you derive it
precisely? The inner loop runs (n-i) times for each i, giving total iterations = n + (n-1) + ... + 1
= n(n+1)/2 = O(n^2). The follow-up: is this the same in practice as a pure nested O(n^2)?
No—the constant is 0.5x, which matters in tight performance constraints.

Amortized Analysis: The Hidden Concept


Amortized analysis measures the average cost per operation over a sequence of operations, not the
worst case of a single operation. This is crucial for understanding dynamic arrays, hash tables, and
stack-based algorithms.

Classic Example: [Link]() — Single add() is O(1) most of the time, but O(n) during resize.
So what's the true complexity?

ArrayList internals (simplified):


- Initial capacity: 10
- On overflow: new array of size * 1.5, copy all elements
- Cost of n adds:
1 + 1 + 1 + ... + 1 (n-1 times) + n (one resize) = 2n - 1
- Amortized cost per add = (2n-1)/n ≈ 2 = O(1) amortized

// Proof via potential method:


// Define Φ(n) = 2*(current_size) - capacity
// Each cheap operation increases Φ by 2
// Expensive resize uses all stored potential

💡 Key Insight
When an interviewer asks 'what's the complexity of add()?', say 'O(1) amortized, O(n) worst-
case, and explain why the average is O(1).' This immediately signals senior-level thinking.

Advanced Java Interview Mastery Handbook


Tricky Big-O Patterns Interviewers Love
The following patterns trip up even experienced candidates:

Pattern Code Pattern Complexity Why It's Tricky


Log loop for(i=1; i<n; i*=2) O(log n) i doubles each time,
so log2(n) iterations
Nested log for(i=n; i>0; i/=2) O(log²n) Both loops are log n
for(j=n; j>0; j/=2) — multiplicative
Dependent nested for(i=0;i<n;i++) O(n²) Sum of 0+1+...+(n-1)
for(j=0;j<i;j++) = n(n-1)/2
Fibonacci recursive fib(n) = fib(n-1)+fib(n- O(2^n) time, O(n) Binary tree of calls,
2) space not O(n)
Binary search T(n) = T(n/2) + O(1) O(log n) Master theorem:
recursive a=1,b=2,f=O(1)
Merge sort T(n) = 2T(n/2) + O(n) O(n log n) Master theorem:
a=2,b=2,f=O(n)

Space Complexity: The Forgotten Dimension


Interviewers at top companies frequently pivot from time complexity to space complexity.
Understanding stack space, heap allocation, and when recursion is secretly O(n) space is critical.

🎯 Tricky Interview Question


'What is the space complexity of reversing a singly linked list recursively?' Most say O(1)
because you're not creating new nodes. WRONG. Recursive reversal creates O(n) call stack
frames. The iterative version is O(1) space. This is a classic trap that tests whether you
understand implicit stack allocation.

// Recursive: O(n) space (n stack frames)


ListNode reverse(ListNode head) {
if (head == null || [Link] == null) return head;
ListNode rest = reverse([Link]); // <-- n recursive calls
[Link] = head;
[Link] = null;
return rest;
}

// Iterative: O(1) space (only 3 pointers)


ListNode reverse(ListNode head) {
ListNode prev = null, curr = head, next = null;
while (curr != null) {
next = [Link];
[Link] = prev;
prev = curr;
curr = next;
}
return prev;
}

Advanced Java Interview Mastery Handbook


1.2 Master Theorem & Recurrence Relations
The Master Theorem solves recurrences of the form T(n) = aT(n/b) + f(n) where a ≥ 1, b > 1.

Case Condition Solution Example


Case 1 f(n) = O(n^(log_b(a) - T(n) = Θ(n^log_b(a)) T(n)=8T(n/2)+n →
ε)) O(n³)
Case 2 f(n) = Θ(n^log_b(a)) T(n) = Θ(n^log_b(a) * T(n)=2T(n/2)+n → O(n
log n) log n)
Case 3 f(n) = Ω(n^(log_b(a) + T(n) = Θ(f(n)) T(n)=T(n/2)+n → O(n)
ε))

💡 Common Mistake
Candidates memorize the three cases but forget to verify the regularity condition for Case 3:
a*f(n/b) ≤ c*f(n) for some c<1. Always verify this when using Case 3.

1.3 Real-World Performance Trade-offs


Theoretical complexity doesn't always match real-world performance. Understanding cache behavior,
branch prediction, and hardware-level impacts separates senior engineers from juniors.

🎯 Tricky Interview Question


'Is O(log n) always faster than O(n) for small n?' — No! For n < 16, linear search on an array
often beats binary search due to CPU cache effects. The array fits in a single cache line (64
bytes = 16 integers). This is why Java uses binary search only for arrays larger than a
threshold.

When O(n²) beats O(n log n): Insertion Sort

// Java's [Link]() uses Dual-Pivot Quicksort for primitives


// BUT switches to Insertion Sort when subarray size ≤ 47
// Why? For small arrays, O(n²) insertion sort is faster because:
// 1. No recursion overhead
// 2. Excellent cache locality (sequential memory access)
// 3. Branch predictor works well on nearly-sorted data
// 4. Low constant factor (simple increment/compare operations)

// TimSort (used for objects) also uses Insertion Sort for 'runs' < 64

Advanced Java Interview Mastery Handbook


CHAPTER 2: Data Structures & Algorithms — Java
Internals Exposed
Understanding Java's collection framework at the implementation level is not optional for senior
roles. This chapter dissects each major structure and reveals the non-obvious design decisions.

2.1 [Link]() — One of Java's Most Clever Implementations


The answer '[Link]() uses Quicksort' is incomplete and will get you challenged in an interview.
The full answer requires understanding dual-pivot partitioning, the adaptive algorithm switching, and
why different behaviors exist for primitives vs objects.

Primitives: Dual-Pivot Quicksort (DPQ)

// Vladimir Yaroslavskiy's Dual-Pivot Quicksort (Java 7+)


// Algorithm chooses TWO pivots: e1 and e2 (e1 ≤ e2)
// Partitions array into THREE regions:
// [less than e1] [e1] [between e1 and e2] [e2] [greater than e2]

// Why 2 pivots? Reduces comparisons by ~20% vs classic Quicksort


// Empirically: average case is 2nln(n) instead of 2.5nln(n)

// THRESHOLD SWITCHING:
// len < 47: Insertion Sort (cache-friendly for tiny arrays)
// len < 286: DPQ without sampling
// len >= 286: Check if nearly sorted → TimSort, else DPQ with 5-element median

💡 Interview Follow-up
Why doesn't [Link]() use Merge Sort for primitives? Merge Sort requires O(n) auxiliary
space. For primitive arrays where stability doesn't matter (primitives have no identity, only
value), DPQ's O(log n) space is preferred.

Objects: TimSort — Adaptive Merge Sort


For object arrays, [Link]() uses TimSort, a hybrid of merge sort and insertion sort invented by
Tim Peters for Python's [Link]().

// TimSort key properties:


// 1. Stable sort (equal elements maintain original order)
// 2. Adaptive: O(n) on nearly-sorted input
// 3. Worst case: O(n log n) — guaranteed

// Algorithm overview:
// 1. Scan for 'runs' (already sorted sequences, including descending)
// 2. Extend short runs using binary insertion sort (minRun = 32-64)
// 3. Merge runs using galloping merge (exponential search for merge point)

// 'Galloping mode': when one run is consistently winning,

Advanced Java Interview Mastery Handbook


// switch from linear to exponential search — O(log k) instead of O(k)
// Reverts to linear when galloping stops being efficient

🎯 Tricky Interview Question


Why is stability required for objects but not primitives? Because objects can be equal in sort
key but different by reference. If you sort Person objects by age, two people aged 25 should
maintain their relative input order. This enables predictable multi-key sorting: first sort by last
name, then by first name — the last-name order is preserved.

Aspect DPQ (primitives) TimSort (objects)


Average Case O(n log n) O(n log n)
Best Case O(n log n) O(n) — nearly sorted
Worst Case O(n log n) O(n log n)
Space O(log n) stack O(n) auxiliary
Stable? No Yes
Small arrays Insertion Sort (<47) Insertion Sort (<minRun)

2.2 HashMap: The Definitive Internal Architecture


HashMap is possibly the most heavily interrogated data structure in Java interviews. The question
chain can go 15+ levels deep. Here is the complete knowledge map.

Core Data Structure

// HashMap is an array of 'buckets', each a linked list (Java 7)


// or linked list/red-black tree (Java 8+)

// Internal structure:
static class Node<K,V> implements [Link]<K,V> {
final int hash; // Cached hash code
final K key;
V value;
Node<K,V> next; // For chaining in same bucket
}

// Default initial capacity: 16


// Default load factor: 0.75
// Treeification threshold: 8 (list → red-black tree)
// Untreeification threshold: 6 (tree → list during shrink)

// Index calculation:
int index = (n - 1) & hash; // n = table length (always power of 2)
// This is equivalent to hash % n but 5x faster (bitwise AND)

Advanced Java Interview Mastery Handbook


The Hash Function — A Masterpiece of Engineering

// Java 8 [Link]() method:


static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = [Link]()) ^ (h >>> 16);
}
// This 'spreads' the high bits of the hash into lower bits
// Why? Because (n-1) & hash only uses low bits when n is small
// Example: n=16, so we only use 4 bits. Without spreading,
// two keys with same low 4 bits would always collide regardless
// of their upper bits. XOR with h>>>16 mixes high entropy.

🎯 Tricky Interview Question


What happens to HashMap when all keys hash to the same bucket? Before Java 8: O(n) for
all operations — becomes a linked list. After Java 8: After 8 elements, the chain converts to a
red-black tree, giving O(log n) worst case. But even O(log n) with hash collision is a security
concern (Hash DoS attacks). Java uses randomized hashing for String keys in some
contexts.

Resize (Rehash) — Critical and Expensive

// Resize triggers when: size > capacity * loadFactor


// Default: size > 16 * 0.75 = 12 elements triggers resize

// Resize process:
// 1. Create new array of size = oldCapacity * 2
// 2. Rehash ALL existing entries into new array
// 3. O(n) operation — this is the expensive part

// Java 8 optimization: entries either stay in same index


// or move to (oldIndex + oldCapacity) — only 1-bit check!
// Because size doubles (power of 2), only the NEW high bit
// of the hash determines bucket assignment.
// Example: capacity 16→32, index bit check: (hash & 16) == 0

💡 Load Factor Trade-off


Lower load factor (e.g., 0.5) means fewer collisions but more memory wasted and more
frequent resizes. Higher load factor (e.g., 0.9) saves memory but increases collision chains.
0.75 is mathematically optimal for balancing time/space — it gives expected constant-time
operations under uniform hashing.

LinkedHashMap vs HashMap vs TreeMap


Operation HashMap LinkedHashMap TreeMap Hashtable
get/put/remove O(1) avg O(1) avg O(log n) O(1) avg

Advanced Java Interview Mastery Handbook


Iteration order Random Insertion order Sorted by key Random
Null keys/values 1 null key, null 1 null key, null No null keys No nulls
values values
Thread-safe? No No No Yes (legacy)
Memory Lower Higher (doubly- Higher (tree Similar to
linked) nodes) HashMap

2.3 Trie (Prefix Tree) — Deep Implementation


Tries are underused but powerful. They appear in autocomplete, spell checkers, IP routing, and any
prefix-based lookup system. Knowing the internal implementation is essential for senior roles.

class TrieNode {
private TrieNode[] children; // 26 for lowercase letters
private boolean isEndOfWord;
private int count; // Optional: frequency count

public TrieNode() {
children = new TrieNode[26];
isEndOfWord = false;
}
}

class Trie {
private TrieNode root = new TrieNode();

// Insert: O(m) where m = word length


public void insert(String word) {
TrieNode node = root;
for (char c : [Link]()) {
int idx = c - 'a';
if ([Link][idx] == null)
[Link][idx] = new TrieNode();
node = [Link][idx];
}
[Link] = true;
}

// Search: O(m) — does NOT depend on dictionary size!


public boolean search(String word) {
TrieNode node = root;
for (char c : [Link]()) {
int idx = c - 'a';
if ([Link][idx] == null) return false;
node = [Link][idx];
}
return [Link];
}

// All words with prefix: O(m + output)


public List<String> autocomplete(String prefix) {
List<String> results = new ArrayList<>();

Advanced Java Interview Mastery Handbook


TrieNode node = getNode(prefix);
if (node != null) dfs(node, prefix, results);
return results;
}
}

🎯 Tricky Interview Question


Memory optimization question: 'Your Trie uses 26 child pointers per node. In a dictionary of
10,000 words, how much memory does this waste?' Answer: Each node has 26 * 8 bytes
(reference) = 208 bytes per node. In a sparse trie, most pointers are null. Solution: Use a
HashMap<Character, TrieNode> as children field — O(actual children) space. Trade-off:
HashMap is slower due to hashing overhead. For Unicode, HashMap is mandatory.

2.4 Thread-Safe Collections — The Complete Map


One of the most commonly confused areas. Candidates mix up which collections are thread-safe,
which are merely synchronized, and which use lock-free algorithms.

Collection Thread Safety Mechanism Best Use Case


ArrayList NOT safe None Single-threaded
Vector Synchronized Synchronized Legacy — avoid
methods
[Link]() Synchronized Wraps with sync Simple shared list
block
CopyOnWriteArrayList Safe Copy-on-write Many reads, few
writes
HashMap NOT safe None Single-threaded
Hashtable Synchronized Synchronized Legacy — avoid
methods
ConcurrentHashMap Safe Segment locking High-concurrency
(J7) / CAS (J8) maps
ConcurrentSkipListMap Safe Lock-free skip list Sorted concurrent
map
BlockingQueue Safe ReentrantLock Producer-consumer
(ArrayBlockingQueue)

ConcurrentHashMap Deep Dive

// Java 7: 16 segments, each with its own ReentrantLock


// Concurrency level = 16 (up to 16 threads write simultaneously)

// Java 8: Lock-free reads, CAS (Compare-And-Swap) for writes


// No segments! Per-bucket synchronization with synchronized(first_node)

// Key insight: Java 8 CHM uses volatile reads for get(),

Advanced Java Interview Mastery Handbook


// NO lock needed for reading! Compare with Hashtable which
// locks the ENTIRE table for every get().

// putIfAbsent, compute, merge — all atomic without external sync


[Link](key, (k, v) -> v == null ? 1 : v + 1); // Atomic increment!

🎯 Tricky Interview Question


'Is iterating over ConcurrentHashMap thread-safe?' Yes, but the iterator is weakly consistent
— it reflects the state at or after creation. It will NOT throw ConcurrentModificationException.
But it may or may not show puts/removes that happen after the iterator starts. This is
different from fail-fast iterators in HashMap which throw ConcurrentModificationException.
Contrast with CopyOnWriteArrayList which gives a SNAPSHOT view at iterator creation
time.

2.5 Comparable vs Comparator — The Complete Picture


This seems simple but has many non-obvious implications for [Link](), TreeSet behavior,
and the violations that cause bugs.

// Comparable: natural ordering, implemented by the class itself


public class Employee implements Comparable<Employee> {
private int id;
private String name;
private double salary;

@Override
public int compareTo(Employee other) {
// MUST return negative, zero, or positive
// DO NOT use subtraction for integers! Integer overflow!
// BAD: return [Link] - [Link]; (overflow if negative IDs)
// GOOD: return [Link]([Link], [Link]);
return [Link]([Link], [Link]);
}
}

// Comparator: external ordering, flexible, composable


Comparator<Employee> bySalaryThenName = Comparator
.comparingDouble(Employee::getSalary) // Primary sort
.reversed() // Descending
.thenComparing(Employee::getName); // Secondary sort

🎯 Tricky Interview Question


'What happens if your Comparator violates the contract (not transitive)?' TreeSet/TreeMap
use the comparator for ALL operations including equals. If your comparator is inconsistent,
elements appear to be lost or duplicated. Example: If compare(a,b)=0 but ![Link](b),
TreeSet will treat them as duplicates and only store one — even though they're different
objects! The TreeSet contract says: 'a set is consistent if and only if ([Link](b)==0) ==
[Link](b) for all a,b.'

Advanced Java Interview Mastery Handbook


Stream API Sorting Performance

// [Link]() with Comparator — pitfalls:

// PROBLEM: This creates a new comparison chain on EVERY comparison!


[Link]()
.sorted((a, b) -> [Link]().compareTo([Link]())) // Lambda recreated each
call
.collect([Link]());

// BETTER: Cache the comparator


Comparator<Employee> BY_NAME = [Link](Employee::getName);
[Link]().sorted(BY_NAME).collect([Link]());

// BEST for parallel streams: ensure Comparator is Serializable


// Parallel sorted() uses fork-join pool, needs to serialize Comparator

2.6 Stream API — Performance and Internal Mechanics


Streams look simple but have a complex execution model. Understanding lazy evaluation, terminal
operations, and parallel stream pitfalls is essential.

Lazy Evaluation: How Streams Actually Work

// Intermediate operations are LAZY — nothing executes until terminal op


Stream<String> stream = [Link]()
.filter(s -> [Link]() > 3) // NOT executed yet
.map(String::toUpperCase); // NOT executed yet

// Terminal operation triggers execution:


long count = [Link](); // NOW filter and map run

// Short-circuit example: findFirst() stops after first match


Optional<String> first = [Link]()
.filter(s -> [Link]('A')) // Only processes until first match!
.map(String::toUpperCase)
.findFirst(); // Terminal: short-circuits

🎯 Tricky Interview Question


'What's wrong with collecting to a list and then streaming again?' [Link]().filter(x -> x >
5).collect(toList()).stream().map(x -> x*2) — this materializes the intermediate result
unnecessarily. The correct approach is to chain operations: [Link]().filter(x -> x >
5).map(x -> x*2). The wrong version uses extra memory and loses the optimization benefits
of lazy evaluation.

Advanced Java Interview Mastery Handbook


Parallel Streams: When They Help and When They Hurt

// Parallel streams use [Link]()


// Default parallelism = CPU cores - 1

// GOOD use case: CPU-intensive, independent operations, large data


long sum = [Link](0, 10_000_000)
.parallel()
.filter(n -> isPrime(n)) // CPU-intensive, independent
.sum();

// BAD use cases:


// 1. Small collections (overhead > benefit, typically < 10,000 elements)
// 2. IO-bound operations (threads block, no speedup)
// 3. Stateful lambdas (AtomicInteger in parallel stream = race condition)
// 4. Ordered streams (sorted parallel stream = expensive merge)

// DANGER: Shared state in parallel stream


List<Integer> results = new ArrayList<>();
[Link](0, 1000).parallel().forEach(i -> [Link](i));
// WRONG: ArrayList is not thread-safe, results will be corrupted
// Fix: collect([Link]()) or use concurrent collector

Advanced Java Interview Mastery Handbook


CHAPTER 3: Object-Oriented Programming — The Deep
Cuts
Everyone claims to know OOP. Interviewers test whether you understand the WHY — why these
principles exist, where they break down, and how they manifest in Java's type system.

3.1 SOLID Principles — What They Actually Mean

Single Responsibility Principle (SRP)


A class should have one reason to change. The word 'reason' is critical — it means one stakeholder
whose requirements would force a change.

// VIOLATION: UserService does too much


class UserService {
User getUser(int id) { ... } // Data access
void sendWelcomeEmail(User u) { ... } // Email logic
String toJsonString(User u) { ... } // Serialization
}

// CORRECT: Each class has one reason to change


class UserRepository { User findById(int id) { ... } }
class EmailService { void sendWelcome(User u) { ... } }
class UserSerializer { String toJson(User u) { ... } }

// Why does this matter? If business changes email templates,


// only EmailService changes. UserRepository is untouched.
// Risk of regression bugs is contained.

🎯 Tricky Interview Question


'Does SRP mean a class can only have one method?' No. A God class with 50 related
methods about user authentication has one responsibility. A class with 2 unrelated methods
violates SRP. The test: list all the stakeholders (product team, infra team, design team) who
could request changes. If multiple answer, it violates SRP.

Open/Closed Principle (OCP)

// VIOLATION: Adding a new shape requires modifying existing code


double area(Shape s) {
if (s instanceof Circle) return [Link] * [Link] * [Link];
if (s instanceof Square) return [Link] * [Link];
// Need to add Triangle here → modifying this method
}

// CORRECT: Extend without modifying


interface Shape { double area(); }
class Circle implements Shape {
public double area() { return [Link] * radius * radius; }

Advanced Java Interview Mastery Handbook


}
// Adding Triangle: implement Shape, zero existing code changes

Liskov Substitution Principle (LSP) — The Most Violated


If S is a subtype of T, objects of type T may be replaced with objects of type S without altering
program correctness. This is violated more often than candidates realize.

// Classic LSP violation: Rectangle-Square problem


class Rectangle {
protected int width, height;
public void setWidth(int w) { [Link] = w; }
public void setHeight(int h) { [Link] = h; }
public int area() { return width * height; }
}

class Square extends Rectangle {


@Override
public void setWidth(int w) { width = height = w; } // VIOLATION!
@Override
public void setHeight(int h) { width = height = h; } // VIOLATION!
}

// Client code breaks:


void testArea(Rectangle r) {
[Link](5); [Link](10);
assert [Link]() == 50; // Fails for Square! area() = 100
}

💡 The Fix
Square should NOT extend Rectangle mathematically, but that doesn't mean inheritance is
wrong here — the problem is mutable setters. If Rectangle and Square are immutable value
objects (final width/height set in constructor), LSP holds because behavior is fully defined at
construction.

Interface Segregation Principle (ISP)


Dependency Inversion Principle (DIP)

// DIP: High-level modules should not depend on low-level modules.


// Both should depend on abstractions.

// VIOLATION: OrderService depends directly on MySQLDatabase


class OrderService {
private MySQLDatabase db = new MySQLDatabase(); // Tight coupling
Order getOrder(int id) { return [Link](...); }
}

// CORRECT: Both depend on abstraction


interface OrderRepository { Order findById(int id); }

Advanced Java Interview Mastery Handbook


class MySQLOrderRepository implements OrderRepository { ... }
class MongoOrderRepository implements OrderRepository { ... }

class OrderService {
private final OrderRepository repo; // Inject via constructor
OrderService(OrderRepository repo) { [Link] = repo; }
Order getOrder(int id) { return [Link](id); }
}
// Now you can swap MySQL for MongoDB with ZERO changes to OrderService

3.2 Polymorphism Edge Cases

Static Binding vs Dynamic Binding

🎯 Tricky Interview Question


What does this print? class Animal { String name() { return 'Animal'; } } class Dog extends
Animal { String name() { return 'Dog'; } } Animal a = new Dog();
[Link]([Link]()); // (1) Dynamic dispatch → 'Dog' class Animal { static String
type() { return 'Animal'; } } class Dog extends Animal { static String type() { return 'Dog'; } }
Animal a = new Dog(); [Link]([Link]()); // (2) Static binding → 'Animal'! Static
methods are bound at compile time based on REFERENCE type, not runtime type. This is
called 'hiding' not 'overriding' — and it's a common interview trap.

// Covariant return types — Java allows since Java 5


class Animal { Animal create() { return new Animal(); } }
class Dog extends Animal {
@Override
Dog create() { return new Dog(); } // LEGAL: Dog extends Animal
}

// Covariant doesn't apply to parameters — that would be overloading!


class Animal { void eat(Animal a) { } }
class Dog extends Animal {
void eat(Dog d) { } // NOT overriding — this is OVERLOADING!
// Both methods exist in Dog. eat(Animal) inherited, eat(Dog) new.
}

3.3 Immutable Class Design — The Complete Recipe


Immutable classes are thread-safe by definition, can be safely shared, and make reasoning about
state much simpler. But getting them truly immutable has subtle requirements.

// Perfect immutable class template:


public final class ImmutablePerson { // 1. FINAL class — no subclassing
private final String name; // 2. FINAL fields

Advanced Java Interview Mastery Handbook


private final int age;
private final List<String> hobbies; // 3. Mutable field — defensive copy!

public ImmutablePerson(String name, int age, List<String> hobbies) {


[Link] = name;
[Link] = age;
// 4. DEFENSIVE COPY in constructor
[Link] = [Link](new ArrayList<>(hobbies));
}

public String getName() { return name; }


public int getAge() { return age; }

// 5. DEFENSIVE COPY in getter too (if not unmodifiable)


public List<String> getHobbies() {
return hobbies; // Safe: already unmodifiable
}
// No setters!
}

🎯 Tricky Interview Question


'I made all fields final and the class final. Is it immutable?' Not necessarily. Consider: private
final int[] scores; Arrays are mutable objects. Even with final reference, getScores()[0] = 999;
modifies the internal state. Fix: return a copy in the getter: return [Link](scores,
[Link]); — or return [Link]() for lists. This is the defensive copy
pattern.

3.4 Abstraction vs Encapsulation — The Common Confusion


Candidates frequently confuse these two concepts or conflate them. They are different and serve
different purposes.

Encapsulation — Bundling data and methods that operate on that data, restricting direct access to
internal state. It's about HIDING IMPLEMENTATION DETAILS from the outside.

Abstraction — Providing a simplified view of a complex system, hiding the complexity itself (not just
the data). It's about defining WHAT something does, not HOW.

// Encapsulation example:
class BankAccount {
private double balance; // Encapsulated — hidden, not directly accessible
public void deposit(double amount) {
if (amount > 0) balance += amount; // Controlled access
}
}

// Abstraction example:
interface PaymentGateway {
boolean processPayment(double amount, String cardNumber);

Advanced Java Interview Mastery Handbook


// The user doesn't know if this uses Stripe, PayPal, or Razorpay
// That complexity is abstracted away
}

💡 Memory Device
Encapsulation = how you protect state (private fields, getters/setters). Abstraction = how you
design contracts (interfaces, abstract classes). A well-designed class uses BOTH: it
abstracts its behavior via interface AND encapsulates its internal state via private fields.

3.5 Object Lifecycle and Memory Management

// Java object lifecycle:


// 1. CLASS LOADING: JVM loads .class file, allocates Class object in metaspace
// 2. MEMORY ALLOCATION: 'new' allocates on heap (Eden space in Young Gen)
// 3. INITIALIZATION: <init> method runs (field assignments, constructor)
// 4. USE: Object referenced, GC roots keep it alive
// 5. UNREACHABLE: No more strong references from GC roots
// 6. GARBAGE COLLECTION: GC reclaims memory
// 7. FINALIZATION: finalize() called before GC (deprecated in Java 9)

// Reference types and GC behavior:


// StrongReference: Object obj = new Object(); → Never GC'd while ref alive
// SoftReference: new SoftReference<>(obj) → GC'd only on memory pressure
// WeakReference: new WeakReference<>(obj) → GC'd on next GC cycle
// PhantomReference: enqueued AFTER GC, for cleanup callbacks

🎯 Tricky Interview Question


'When is finalize() called?' Finalize() is called by the GC before reclaiming memory — but
this is non-deterministic! You cannot rely on it for resource cleanup (like closing file handles).
This is why Java 7 introduced try-with-resources and AutoCloseable. In Java 9+, finalize() is
deprecated. Java 18 removes it. Always use Cleaner or PhantomReference for cleanup
callbacks.

Advanced Java Interview Mastery Handbook


CHAPTER 4: Java Internals — JVM, GC, ClassLoader,
and Beyond
This chapter covers what happens under the hood of the JVM. Questions here are increasingly
common at FAANG and product-based companies as systems grow in scale and performance
requirements tighten.

4.1 JVM Architecture — The Complete Picture

Memory Areas (JVM Runtime Data Areas)


Memory Area Per-Thread? Contents Common Issues
Heap Shared All objects, class OutOfMemoryError,
instances GC pressure
Metaspace (Java 8+) Shared Class metadata, Classloader leaks,
method bytecode MetaspaceOOM
Stack Per-thread Stack frames, local StackOverflowError
vars, partial results (deep recursion)
PC Register Per-thread Current instruction None normally
pointer
Native Method Stack Per-thread Native (C/C++) Native library issues
method calls
Code Cache Shared JIT-compiled native Code cache full
code warning

Heap Memory Generations

// Heap is divided into generations based on object lifetime


// (G1GC and ZGC use different models, but this is the foundation)

// Young Generation:
// Eden Space: New objects allocated here (fast bump-pointer allocation)
// Survivor S0 (From): Survived objects move here from Eden
// Survivor S1 (To): Rotates with S0 after each minor GC

// Old Generation (Tenured): Objects that survived multiple GC cycles

// Minor GC (Young Gen):


// 1. Mark all objects in Eden/S0 reachable from GC roots
// 2. Copy live objects to S1 (compact — no fragmentation!)
// 3. Clear Eden and S0
// 4. Objects > MaxTenuringThreshold move to Old Gen

// Major GC / Full GC (Old Gen): Much slower, 'Stop The World' pause

// Key JVM flags:


// -Xms512m Initial heap size
// -Xmx2g Maximum heap size

Advanced Java Interview Mastery Handbook


// -XX:NewRatio=3 Old:Young ratio = 3:1
// -XX:SurvivorRatio=8 Eden:Survivor = 8:1

🎯 Tricky Interview Question


'Why do we have generations at all? Why not just one heap?' The Generational Hypothesis:
most objects die young ('infant mortality'). Empirically, 95%+ of objects become unreachable
within milliseconds of creation. By separating young objects (minor GC runs frequently on
small space, very fast) from old objects (rarely GC'd), we avoid repeatedly scanning long-
lived objects. Without generations, every GC would scan the entire heap — too slow.

4.2 Garbage Collection Algorithms

G1GC (Garbage-First) — Default since Java 9

// G1GC key concepts:


// - Divides heap into equal-sized REGIONS (~2048 regions, configurable)
// - Each region can be Eden, Survivor, Old, or Humongous
// - 'Garbage First': prioritizes regions with most garbage

// G1GC cycle:
// 1. Initial Mark (STW): Mark GC roots (very fast)
// 2. Concurrent Root Region Scan: Scan survivor regions
// 3. Concurrent Mark: Mark all live objects (concurrent with app)
// 4. Remark (STW): Complete marking (SATB algorithm)
// 5. Cleanup (STW): Calculate liveness, sort regions by GC efficiency
// 6. Mixed GC: Collect best Young + best Old regions

// Goal: Predictable pause times (-XX:MaxGCPauseMillis=200)


// G1 adjusts region collection to hit pause time target

ZGC and Shenandoah — Low-Latency Collectors

// ZGC (Java 15+ production-ready):


// - Concurrent compaction (no Stop-The-World compaction!)
// - Sub-millisecond pauses even on terabyte heaps
// - Uses colored pointers (64-bit metadata in object references)
// - Load barriers: every object access checked if pointer is valid
// Tradeoff: ~10-15% higher CPU overhead, some throughput loss

// When to choose which GC:


// G1GC: General purpose, 2-4GB heaps, default recommendation
// ZGC: Latency-sensitive (trading systems, real-time apps), large heaps
// Parallel GC: Maximum throughput (batch processing), latency not critical
// Shenandoah: Low latency without ZGC's memory overhead

Advanced Java Interview Mastery Handbook


GC Algorithm Pause Type Pause Duration Throughput Best For
Serial GC Full STW Seconds Moderate Single-core,
small apps
Parallel GC Full STW Seconds High Batch,
throughput
priority
G1GC Incremental STW Hundreds of ms Good General purpose
ZGC Near-concurrent <1ms Lower Latency-critical
apps
Shenandoah Near-concurrent <10ms Moderate Low latency,
lower memory

4.3 ClassLoader Mechanism

// Three-tier classloader hierarchy:


// 1. Bootstrap ClassLoader (native C++ in JVM)
// Loads: [Link], [Link].*, [Link].*, etc.
// Parent: null (no parent — it IS the root)

// 2. Extension (Platform) ClassLoader (Java 9+)


// Loads: jre/lib/ext/*.jar, java modules

// 3. Application ClassLoader
// Loads: your classes, classpath entries

// Parent Delegation Model:


// When a class needs loading, classloader FIRST asks parent.
// If parent can't find it, child attempts. This prevents
// application code from overriding [Link]!

// Custom ClassLoader example (plugin systems, hot-reload):


public class PluginClassLoader extends URLClassLoader {
public PluginClassLoader(URL[] urls) {
super(urls, [Link]());
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// Override delegation for plugin classes only
if ([Link]('[Link].')) {
return findClass(name); // Load ourselves, skip parent
}
return [Link](name, resolve); // Delegate for rest
}
}

🎯 Tricky Interview Question

Advanced Java Interview Mastery Handbook


'What is a ClassLoader leak and how does it happen?' In application servers (Tomcat,
JBoss), each web app has its own ClassLoader. When the app is undeployed, the
ClassLoader should be GC'd. But if ANY object in the Old Generation holds a reference to a
class loaded by that ClassLoader, the entire ClassLoader graph (and all its classes) stays in
memory. Common causes: static references in third-party libraries, ThreadLocal variables
not cleaned up, shutdown hooks registered by the app. Fix: use weak references for cross-
ClassLoader references.

4.4 String Pool Internals

// String Pool (String Intern Pool) lives in Heap (Java 7+)


// Before Java 7: it was in PermGen — could cause PermGen OOM!

// Literal strings: automatically interned


String a = 'hello'; // Goes to pool
String b = 'hello'; // Returns same pooled reference
[Link](a == b); // true! Same object

// new String(): NOT interned automatically


String c = new String('hello'); // New object on heap
[Link](a == c); // false! Different objects
[Link]([Link](c)); // true! Same content

// Manual interning:
String d = [Link](); // Look up/add to pool, return pool reference
[Link](a == d); // true! Now same pooled object

// [Link]() performance:
// - Useful for reducing memory when storing millions of duplicate strings
// - But intern() itself is expensive (hash table lookup in native code)
// - Consider ConcurrentHashMap<String, String> as faster alternative

🎯 Tricky Interview Question


'Why does Java use '==' for reference equality and not value equality?' Because == is O(1)
and equals() can be O(n) for strings. If == checked content, every string comparison
(including == on Integer, == on references) would be O(n). Java chose explicitness: use
equals() when you mean content equality. This is a design decision that's been criticized but
has a clear rationale.

4.5 Reflection and Its Performance Impact

// Reflection bypasses compile-time checks


Class<?> clazz = [Link]('[Link]');
Method method = [Link]('calculate', [Link]);
Object result = [Link](instance, 42);

// Performance cost of reflection:


// 1. [Link](): ClassLoader lookup, expensive first call

Advanced Java Interview Mastery Handbook


// 2. getMethod(): Scans methods, no JIT optimization
// 3. [Link](): Cannot be inlined by JIT (indirect dispatch)
// → ~50-100x slower than direct method calls

// Optimization: Cache Method objects!


private static final Method CALCULATE;
static {
CALCULATE = [Link]('calculate', [Link]);
[Link](true); // Bypass security check (costly if not set)
}

// Modern alternative: MethodHandles (Java 7+)


// MethodHandle is JIT-optimizable, near native performance
MethodHandle mh = [Link]()
.findVirtual([Link], 'calculate', [Link]([Link],
[Link]));
int result = (int) [Link](instance, 42); // ~2-3x slower than direct (vs 50-
100x)

Advanced Java Interview Mastery Handbook


CHAPTER 5: Concurrency & Multithreading — Where
Interviews Get Hard
Concurrency is where most candidates reveal gaps in their understanding. The interviewer's goal is
to find where your mental model breaks down. This chapter targets the exact failure points.

5.1 Java Memory Model (JMM) — The Foundation


Without understanding JMM, all your concurrency knowledge is built on sand. JMM defines what
values a thread can see from shared memory.

// The visibility problem:


class StopThread {
private static boolean stopRequested = false;

public static void main(String[] args) throws InterruptedException {


Thread background = new Thread(() -> {
int i = 0;
while (!stopRequested) // May NEVER see stopRequested=true!
i++;
});
[Link]();
[Link](1000);
stopRequested = true; // Written by main thread
// Without volatile, background thread may run forever!
}
}
// JVM is allowed to hoist the read of stopRequested out of the loop
// (loop optimization: if(!stopRequested) while(true)...)
// volatile prevents this hoisting and ensures visibility

🎯 Tricky Interview Question


'Is volatile the same as synchronized?' No. volatile guarantees visibility and ordering
(happens-before), but NOT atomicity for compound operations. volatile int count; count++ is
NOT thread-safe! count++ is three operations: read, increment, write. Two threads can both
read 5, both compute 6, both write 6 — losing one increment. Use AtomicInteger for atomic
compound operations, synchronized for compound multi-variable operations.

Happens-Before Relationship

// The JMM's guarantee: if operation A happens-before B,


// all writes by A are visible to B.

// What creates happens-before?


// 1. Program order: each action in a thread HB later actions
// 2. Monitor lock: unlock HB next lock of same monitor
// 3. Volatile: write to volatile HB subsequent reads of same volatile
// 4. Thread start: [Link]() HB first action in new thread
// 5. Thread join: all actions in T HB [Link](T) returning

Advanced Java Interview Mastery Handbook


// 6. Transitivity: if A HB B and B HB C, then A HB C

// Safe publication via volatile:


class Singleton {
private volatile static Singleton instance; // VOLATILE required
public static Singleton getInstance() {
if (instance == null) { // First check (no lock)
synchronized ([Link]) {
if (instance == null) // Second check (with lock)
instance = new Singleton(); // Volatile ensures full init
visible
}
}
return instance;
}
}
// Without volatile: another thread could see a partially constructed
// Singleton due to instruction reordering! Constructor writes could
// be reordered after the reference assignment.

5.2 Locks, Synchronization, and Deadlocks

ReentrantLock vs synchronized
Feature synchronized ReentrantLock
Acquisition on timeout No tryLock(timeout)
Interruptible wait No lockInterruptibly()
Fairness No (JVM-dependent) new ReentrantLock(true)
Multiple conditions 1 (wait/notify) Multiple Condition objects
Performance JVM-optimized biased locking Slightly more overhead
Debugging Harder Easier (isLocked(), etc.)

// ReentrantLock best practice: ALWAYS use try-finally


ReentrantLock lock = new ReentrantLock();
[Link]();
try {
// Critical section
} finally {
[Link](); // MUST be in finally — exception won't leave lock held
}

// Multiple conditions (classic producer-consumer):


ReentrantLock lock = new ReentrantLock();
Condition notFull = [Link]();
Condition notEmpty = [Link]();

void produce(T item) {


[Link]();

Advanced Java Interview Mastery Handbook


try {
while (isFull()) [Link](); // Specific condition!
[Link](item);
[Link](); // Signal only consumers, not all
} finally { [Link](); }
}

Deadlock Prevention

// Deadlock example:
// Thread 1: lock(A) → lock(B)
// Thread 2: lock(B) → lock(A) → Deadlock!

// Prevention strategies:

// 1. Lock ordering: always acquire locks in same global order


void transfer(Account from, Account to, double amount) {
Account first = [Link] < [Link] ? from : to; // Consistent order!
Account second = [Link] < [Link] ? to : from;
synchronized(first) {
synchronized(second) {
[Link](amount);
[Link](amount);
}
}
}

// 2. tryLock with timeout:


if ([Link](100, [Link])) {
try {
if ([Link](100, [Link])) {
try { /* both held */ } finally { [Link](); }
}
} finally { [Link](); }
} else { /* retry or fail gracefully */ }

🎯 Tricky Interview Question


'What are the four necessary conditions for deadlock?' 1. Mutual Exclusion: Resources held
exclusively 2. Hold and Wait: A process holds a resource while waiting for another 3. No
Preemption: Resources cannot be forcibly taken 4. Circular Wait: A circular chain of
processes each waiting for next Breaking ANY ONE condition prevents deadlock. Lock
ordering breaks Circular Wait. tryLock with timeout effectively breaks Hold and Wait (you
release and retry).

5.3 CountDownLatch, CyclicBarrier, Semaphore, and Phaser

// CountDownLatch: one-time gate


// Use: wait for N tasks to complete before proceeding
CountDownLatch latch = new CountDownLatch(5);

Advanced Java Interview Mastery Handbook


for (int i = 0; i < 5; i++) {
[Link](() -> {
doWork();
[Link](); // Decrement counter
});
}
[Link](); // Main thread blocks until count reaches 0

// CyclicBarrier: reusable meeting point


// Use: N threads must all reach a point before any can proceed
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
[Link]('All threads at barrier, proceeding!');
});
// Each thread calls [Link]() — last one triggers barrierAction

// Key difference: CountDownLatch is single-use, counts DOWN.


// CyclicBarrier resets and can be reused, threads count UP to N.

// Semaphore: limit concurrent access to a resource


Semaphore semaphore = new Semaphore(3); // Max 3 concurrent access
[Link]();
try { accessDatabase(); } finally { [Link](); }

5.4 ThreadLocal — Utility and Danger

// ThreadLocal: each thread has its own value


private static final ThreadLocal<DateFormat> DATE_FORMAT =
[Link](() -> new SimpleDateFormat('yyyy-MM-dd'));

// Usage — no synchronization needed!


String formatted = DATE_FORMAT.get().format(date);

// WHY THIS WORKS: SimpleDateFormat is NOT thread-safe.


// With ThreadLocal, each thread has its own instance.
// No shared state = no synchronization needed.

// CRITICAL DANGER: ThreadLocal in thread pools


// Threads in pools are REUSED. If you set a ThreadLocal value
// in thread 1 of the pool without removing it, the NEXT task
// run on that thread sees the old value!

// ALWAYS clean up in finally:


try {
[Link](someValue);
doWork();
} finally {
[Link](); // CRITICAL: Prevents data leaks and memory leaks
}

💡 Memory Leak Warning

Advanced Java Interview Mastery Handbook


ThreadLocal with thread pools can cause memory leaks. Each Thread holds a
ThreadLocalMap. If you store large objects and never call remove(), those objects stay in
memory as long as the thread lives. In thread pools, threads live forever — so the leak is
permanent.

5.5 CompletableFuture — Async Java

// CompletableFuture: non-blocking async computation


CompletableFuture<User> userFuture = CompletableFuture
.supplyAsync(() -> fetchUser(userId)) // Runs in ForkJoinPool
.thenApplyAsync(user -> enrichWithOrders(user)) // Chain async
.exceptionally(ex -> handleError(ex)); // Error handling

// Combining futures:
CompletableFuture<String> result = CompletableFuture
.allOf(future1, future2, future3) // Wait for ALL
.thenApply(v -> combineResults([Link](), [Link]()));

[Link](fast, slow) // First to complete wins

// PITFALL: thenApply vs thenApplyAsync


// thenApply: runs in SAME thread as previous stage (may block!)
// thenApplyAsync: runs in ForkJoinPool (truly async)
// thenApplyAsync(fn, executor): runs in YOUR executor

🎯 Tricky Interview Question


'What thread does the callback in thenApply() run on?' It depends on timing! If the future is
already complete by the time thenApply() is called, the callback runs on the CALLING thread.
If the future completes later, the callback runs on the COMPLETING thread (the async
worker). This non-determinism is why thenApplyAsync() with an explicit executor is preferred
for long-running callbacks.

Advanced Java Interview Mastery Handbook


CHAPTER 6: System Design — Java Backend
Perspective
System design interviews test your ability to think at scale. This chapter provides the frameworks and
Java-specific implementation details that separate strong candidates from weak ones.

6.1 Designing a Rate Limiter


This is a canonical system design question that tests algorithmic thinking, distributed systems
knowledge, and practical implementation.

Common Algorithms
Algorithm Memory Burst Handling Implementation Best Use Case
Complexity
Token Bucket O(1) per user Allows bursts up Low APIs with burst
to bucket size tolerance
Leaky Bucket O(1) per user Smooths bursts, Low Smooth traffic
fixed rate output flow
Fixed Window O(1) per user Double rate at Very Low Simple,
Counter window edges approximate
limits
Sliding Window O(n) per user Precise, no edge Medium Accurate,
Log issues memory-rich
Sliding Window O(1) per user Approximate, Medium Production: best
Counter very accurate trade-off

Java Implementation: Token Bucket

import [Link];
import [Link];

public class TokenBucketRateLimiter {


private final int capacity;
private final int refillRatePerSecond;
// userId → [tokens, lastRefillTimestamp]
private final ConcurrentHashMap<String, long[]> buckets = new
ConcurrentHashMap<>();

public TokenBucketRateLimiter(int capacity, int refillRatePerSecond) {


[Link] = capacity;
[Link] = refillRatePerSecond;
}

public synchronized boolean allowRequest(String userId) {


long now = [Link]();
long[] bucket = [Link](userId,
k -> new long[]{capacity, now});

Advanced Java Interview Mastery Handbook


// Refill tokens based on elapsed time
long elapsed = now - bucket[1];
long tokensToAdd = (elapsed / 1000) * refillRatePerSecond;
bucket[0] = [Link](capacity, bucket[0] + tokensToAdd);
if (tokensToAdd > 0) bucket[1] = now;

if (bucket[0] > 0) { bucket[0]--; return true; }


return false; // Rate limit exceeded
}
}

💡 Distributed Rate Limiter


For distributed systems, store tokens in Redis with Lua scripts for atomic token operations.
Lua scripts execute atomically in Redis, preventing race conditions across multiple
application servers. Redis + Lua = the industry standard for distributed rate limiting.

6.2 Designing a URL Shortener

Core Design Decisions

// Key requirements:
// - Shorten: longUrl → shortCode (e.g., [Link]/abc123)
// - Redirect: shortCode → 301/302 redirect to longUrl
// - Scale: 100M URLs, 10B redirects/day

// Short code generation options:

// Option 1: MD5/SHA256 + take first 6 chars


// - Problem: Collisions (birthday paradox: √(62^6) ≈ 57,000 URLs before ~1%
collision)

// Option 2: Base62 encoding of auto-increment ID (RECOMMENDED)


// - 6 chars of base62 = 62^6 = ~56 billion unique URLs
// - Deterministic, no collision handling needed

public String encode(long id) {


String chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
StringBuilder sb = new StringBuilder();
while (id > 0) {
[Link]([Link]((int)(id % 62)));
id /= 62;
}
return [Link]().toString();
}

// Option 3: Random 6-char code + check DB for collision


// - Better: Pre-generate codes in batches, store in 'available codes' queue

Advanced Java Interview Mastery Handbook


System Architecture

// Read-heavy system (redirect >> shorten)


// Design: optimize for reads

// 1. Multiple read replicas for URL lookup


// 2. Redis cache for hot URLs (LRU eviction)
// Cache hit rate: ~80% (Pareto: 20% URLs get 80% traffic)

// 301 vs 302 redirect:


// 301 Permanent: Browser caches → no future requests to our server
// Pro: Reduces server load. Con: Can't track analytics.
// 302 Temporary: Browser always hits our server
// Pro: Full analytics tracking. Con: Higher server load.

// Database schema:
// CREATE TABLE urls (
// id BIGINT PRIMARY KEY AUTO_INCREMENT,
// short_code VARCHAR(10) UNIQUE NOT NULL,
// long_url TEXT NOT NULL,
// created_at TIMESTAMP,
// expiry_at TIMESTAMP,
// user_id BIGINT,
// click_count BIGINT DEFAULT 0
// );

// Scaling: Shard by short_code first char → 62 shards possible

6.3 Caching Strategies

Strategy Description Pros Cons Use Case


Cache-Aside App reads cache; Cache only Cold start, data Most common
(Lazy) miss → load from what's needed staleness pattern
DB, write to
cache
Write-Through Write to cache Cache always Write latency Write-heavy,
AND DB consistent doubled strong
simultaneously consistency
Write-Behind Write to cache; Low write latency Risk of data loss High-write, loss-
(Async) async write to DB on crash tolerant
later
Read-Through Cache sits in Simple app code Need smart When cache is
front of DB, caching layer central infra
handles misses
Refresh-Ahead Proactively Low latency for Wasted Predictable
refresh before hot data refreshes for cold access patterns
expiry data

Advanced Java Interview Mastery Handbook


Cache Eviction Policies

// LRU (Least Recently Used) — most common


// Implementation: LinkedHashMap with accessOrder=true
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
LRUCache(int capacity) {
super(capacity, 0.75f, true); // accessOrder = true!
[Link] = capacity;
}
@Override
protected boolean removeEldestEntry([Link]<K, V> eldest) {
return size() > capacity; // Evict when over capacity
}
}

// LFU (Least Frequently Used): Better for non-uniform access


// but requires frequency tracking — more complex O(1) with min-heap

// Cache stampede (thundering herd):


// Many requests for expired key hit DB simultaneously
// Fix: probabilistic early expiration or mutex/locking on miss

6.4 Microservices vs Monolith

Dimension Monolith Microservices


Development Speed Fast initially Slow initially, faster later
Deployment Simple, single unit Complex, independent deploys
Scalability Scale entire app Scale individual services
Technology Single tech stack Polyglot possible
Testing Easier integration tests Harder — need contract tests
Latency In-process calls Network calls overhead
Data Single DB (ACID) Multiple DBs (eventual
consistency)
Team Size Works well for small teams Better for large, independent
teams
Debugging Easier tracing Needs distributed tracing

🎯 Tricky Interview Question


'When would you NOT choose microservices?' Microservices add distributed systems
complexity: network failures, partial failures, eventual consistency, service discovery,
distributed tracing, and increased operational overhead. For a team of 2-5 people, a well-
modularized monolith is almost always better. Amazon, Netflix, and Uber moved to
microservices when they had hundreds of engineers. Start with a modular monolith, split
when you have clear domain boundaries AND team scaling needs.

Advanced Java Interview Mastery Handbook


6.5 Load Balancing Algorithms
Algorithm How It Works Best For Limitation
Round Robin Rotate through Homogeneous Ignores server
servers equally servers, uniform load/capacity
requests
Weighted Round Rotate by assigned Heterogeneous server Static weights, not
Robin weights capacities adaptive
Least Connections Route to server with Long-lived Doesn't consider
fewest active connections request complexity
connections
IP Hash Hash client IP to Session affinity, sticky Uneven distribution if
determine server sessions few client IPs
Random Random server Simple, no state No optimal load
selection needed distribution
Least Response Time Route to fastest- Latency-sensitive Requires health check
responding server apps overhead

Advanced Java Interview Mastery Handbook


CHAPTER 7: Design Patterns — The Java
Implementation Guide
Design patterns are recurring solutions to common design problems. Interviewers ask about them in
two ways: theoretical ('what is the difference between Factory and Abstract Factory?') and applied
('how would you implement this feature?'). This chapter covers both.

7.1 Creational Patterns

Singleton — The Most Argued Pattern

// Thread-safe Singleton options:

// 1. Eager initialization (simplest, thread-safe):


public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() { return INSTANCE; }
}
// Problem: created even if never used. Fine for lightweight singletons.

// 2. Double-Checked Locking (lazy, thread-safe):


public class DCLSingleton {
private volatile static DCLSingleton instance; // VOLATILE required!
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized([Link]) {
if (instance == null) instance = new DCLSingleton();
}
}
return instance;
}
}

// 3. Initialization-on-demand holder (BEST — lazy, thread-safe, elegant):


public class HolderSingleton {
private static class Holder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() { return [Link]; }
}
// JVM guarantees class initialization is thread-safe.
// Holder is loaded only when getInstance() is first called.

🎯 Tricky Interview Question


'How do you break the Singleton pattern in Java?' — THREE ways: 1. Reflection:
getDeclaredConstructor().setAccessible(true).newInstance() bypasses private constructor 2.
Serialization/Deserialization: readObject() creates a new instance unless you implement
readResolve() 3. Cloning: If your class implements Cloneable without overriding clone() Fix:

Advanced Java Interview Mastery Handbook


Use Enum-based Singleton — immune to all three attacks! public enum EnumSingleton {
INSTANCE; }

Builder Pattern — Java Best Practices

// Solves: constructors with many parameters (telescoping constructors)


public class HttpRequest {
private final String url; // Required
private final String method; // Required
private final Map<String, String> headers; // Optional
private final String body; // Optional
private final int timeout; // Optional with default

private HttpRequest(Builder b) {
[Link] = [Link]; [Link] = [Link];
[Link] = [Link]; [Link] = [Link];
[Link] = [Link];
}

public static class Builder {


private final String url;
private final String method;
private Map<String, String> headers = new HashMap<>();
private String body = '';
private int timeout = 5000;

public Builder(String url, String method) {


[Link] = url; [Link] = method;
}
public Builder header(String k, String v) { [Link](k, v); return this;
}
public Builder body(String body) { [Link] = body; return this; }
public Builder timeout(int ms) { [Link] = ms; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}
// Usage:
HttpRequest req = new [Link]('[Link] 'GET')
.header('Auth', 'Bearer token')
.timeout(3000)
.build();

7.2 Structural Patterns

Proxy Pattern — The Power Behind Spring AOP

// Three types of proxies in Java:

// 1. Static Proxy: explicit wrapper class


// 2. JDK Dynamic Proxy: interface-based runtime proxy

Advanced Java Interview Mastery Handbook


// 3. CGLIB Proxy: subclass-based runtime proxy (used by Spring for classes)

// JDK Dynamic Proxy (Spring @Transactional, @Cacheable internals):


interface UserService { User findById(int id); }

UserService proxy = (UserService) [Link](


[Link](),
new Class[]{ [Link] },
(proxyObj, method, args) -> {
[Link]('Before: ' + [Link]());
Object result = [Link](realService, args);
[Link]('After: ' + [Link]());
return result;
}
);

// This is EXACTLY how Spring AOP works!


// @Transactional creates a proxy that wraps your method
// in beginTransaction() ... commit() / rollback()

// IMPORTANT: Spring AOP limitation — self-invocation


// If transactional method A() calls B() in the SAME class,
// the proxy is bypassed! B() is called directly, @Transactional on B ignored!

🎯 Tricky Interview Question


'Why does @Transactional not work when called from within the same class?' Because
Spring AOP uses proxies. When external code calls [Link](), it goes through
the proxy which applies transaction logic. When methodA() calls [Link](), it's a direct
call — this refers to the real object, not the proxy. The proxy is bypassed entirely. Fix: inject
the service into itself (self-injection) or use [Link]().

Decorator Pattern vs Inheritance

// Java I/O streams use Decorator pattern extensively:


BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream('[Link]'), 'UTF-8'));
// FileInputStream: basic byte reading
// InputStreamReader: adds char encoding (byte → char)
// BufferedReader: adds buffering and readLine()
// Each decorates the previous — you can swap components!

// Vs. Inheritance: Decorator adds behavior at runtime, inheritance at compile-time


// 8 possible combinations of Buffer+Encode+Compress?
// Inheritance: 8 classes. Decorator: 3 classes + composition.

7.3 Behavioral Patterns

Advanced Java Interview Mastery Handbook


Observer Pattern — The Event System Foundation

// Foundation of: Java Event Listeners, RxJava, Spring Events, Kafka

// Modern Java implementation using functional interfaces:


public class EventBus<T> {
private final List<Consumer<T>> listeners = new CopyOnWriteArrayList<>();

public void subscribe(Consumer<T> listener) {


[Link](listener);
}

public void unsubscribe(Consumer<T> listener) {


[Link](listener);
}

public void publish(T event) {


[Link](listener -> {
try { [Link](event); }
catch (Exception e) { /* don't let one bad listener block others */ }
});
}
}

// Usage:
EventBus<UserCreatedEvent> bus = new EventBus<>();
[Link](event -> [Link]([Link]()));
[Link](event -> [Link](event));
[Link](new UserCreatedEvent(newUser));

Strategy Pattern — Runtime Algorithm Switching

// Used in: [Link](), [Link]() with Comparator,


// Spring Security AuthenticationStrategy, etc.

@FunctionalInterface
interface SortStrategy {
void sort(int[] arr);
}

class Sorter {
private SortStrategy strategy;

public Sorter(SortStrategy strategy) { [Link] = strategy; }

public void setStrategy(SortStrategy strategy) { [Link] = strategy; }

public void sort(int[] arr) { [Link](arr); }


}

// With Java 8 lambdas, Strategy is just passing a function:


Sorter sorter = new Sorter(arr -> [Link](arr));
[Link](arr -> bubbleSort(arr)); // Switch at runtime

Advanced Java Interview Mastery Handbook


Advanced Java Interview Mastery Handbook
CHAPTER 8: Agile, CI/CD, and Engineering Excellence
Product-based companies evaluate software engineers not just on code, but on engineering culture.
Questions about Agile, CI/CD, and clean code demonstrate how you think about the software
development lifecycle.

8.1 Agile vs Scrum vs Kanban — The Hierarchy

// Agile: PHILOSOPHY / VALUE SYSTEM


// - Individuals and interactions over processes and tools
// - Working software over comprehensive documentation
// - Customer collaboration over contract negotiation
// - Responding to change over following a plan

// Scrum: FRAMEWORK that implements Agile


// - Sprints (1-4 weeks) with defined ceremonies
// - Roles: Product Owner, Scrum Master, Dev Team
// - Ceremonies: Sprint Planning, Daily Standup, Sprint Review, Retrospective
// - Artifacts: Product Backlog, Sprint Backlog, Increment
// - Best for: projects with evolving requirements

// Kanban: FLOW-BASED method


// - Visualize workflow (Kanban board)
// - Limit Work In Progress (WIP limits)
// - Manage flow — optimize throughput
// - No sprints, no roles, continuous delivery
// - Best for: operational work, support, maintenance

🎯 Tricky Interview Question


'What's the difference between Scrum and Kanban?' — Both are Agile but serve different
needs. Scrum has sprints with velocity tracking, good for feature development in chunks.
Kanban has no sprints, uses WIP limits and cycle time metrics, better for continuous support
work. Many teams use Scrumban — sprints from Scrum, WIP limits from Kanban.

8.2 CI/CD — Concepts and Practices

// CI (Continuous Integration):
// - Developers merge code to main branch frequently (daily+)
// - Each merge triggers automated: build → test → code quality checks
// - Goal: Detect integration bugs EARLY when they're cheap to fix
// - Key tools: Jenkins, GitHub Actions, GitLab CI, CircleCI

// CD (Continuous Delivery):
// - Every build that passes CI is DEPLOYABLE to production
// - Deployment is manual (intentional human gate)

Advanced Java Interview Mastery Handbook


// CD (Continuous Deployment):
// - Every passing build is AUTOMATICALLY deployed to production
// - Requires excellent test coverage and feature flags

// Java CI pipeline example (GitHub Actions):


// on: [push, pull_request]
// jobs:
// build:
// steps:
// - checkout
// - setup Java 17
// - mvn compile
// - mvn test (unit tests)
// - mvn verify (integration tests)
// - SonarQube analysis
// - Build Docker image
// - Push to registry
// - Deploy to staging

Feature Flags — Enabling Continuous Deployment Safely

// Feature flags let you deploy code without releasing features


// Enables: gradual rollout, A/B testing, instant rollback

// Simple Java implementation:


public class FeatureFlag {
private static final Map<String, Boolean> flags = new HashMap<>();

static {
[Link]('NEW_PAYMENT_FLOW', false); // Off in production
[Link]('DARK_MODE', true);
}

public static boolean isEnabled(String feature) {


return [Link](feature, false);
}
}

// Usage:
if ([Link]('NEW_PAYMENT_FLOW')) {
return [Link](payment);
} else {
return [Link](payment);
}

// Production: LaunchDarkly, [Link], or custom Redis-backed flags

8.3 Clean Code Principles — The Non-Negotiables

Principle What It Means Java Example (Bad → Good)

Advanced Java Interview Mastery Handbook


Meaningful Names Names reveal intent data → userOrdersByDate
Small Functions Do one thing only processOrderAndSendEmailAndLog()
→ three separate methods
No Magic Numbers Constants with names if(x > 7) → if(x >
MAX_RETRY_COUNT)
DRY (Don't Repeat Every piece of knowledge Copy-paste → extract to utility
Yourself) once method
YAGNI You Aren't Gonna Need It Don't build features 'just in case'
Fail Fast Validate early, fail loudly Null checks at method entry with
clear messages

// BAD: What does this do?


public int calc(int a, int b, int c) {
return a + b * c / 100;
}

// GOOD: Self-documenting code


public double calculateDiscountedPrice(double originalPrice,
double discountPercent,
int quantity) {
double discount = originalPrice * (discountPercent / 100.0);
return (originalPrice - discount) * quantity;
}

// Comments should explain WHY, not WHAT:


// BAD: i++; // increment i by 1
// GOOD: retryCount++; // Exponential backoff requires tracking retry depth

8.4 Code Review Best Practices


Senior engineers are judged heavily on how they do code reviews. This demonstrates mentorship
capability and technical leadership.

Effective code review focuses on: correctness (does it work for all edge cases?), security (SQL
injection, XSS, insecure deserialization), performance (N+1 queries, unnecessary object creation),
maintainability (will a new team member understand this in 6 months?), and test coverage (are edge
cases tested?).

🎯 Tricky Interview Question


'How do you handle disagreement in a code review?' This tests emotional intelligence as
much as technical skill. Strong answer: Start with questions ('What was the reasoning behind
this approach?'), distinguish between style preferences and correctness/security issues, cite
specific principles or benchmarks rather than opinions, and be willing to defer on style if
there's no objective winner. Use a linting tool to remove ALL style debates from human
reviews.

Advanced Java Interview Mastery Handbook


Advanced Java Interview Mastery Handbook
CHAPTER 9: The 50 Trickiest Java Interview Questions
These questions are specifically designed to expose gaps in understanding. Each appears simple on
the surface but has subtleties that most candidates miss.

9.1 Output Prediction Questions

Q: What does this print? Integer a = 127; Integer b = 127; [Link](a ==


b); Integer c = 128; Integer d = 128; [Link](c == d);

true, then false! Java caches Integer objects for values -128 to 127 (Integer Cache). For
values in this range, == compares the SAME cached object. For 128, new Integer
objects are created each time, so == compares different references. Always use
.equals() for Integer comparison. The cache range is configurable via -
XX:AutoBoxCacheMax.

Q: What does this print? String s1 = new String('java'); String s2 = new


String('java'); [Link](s1 == s2); [Link]([Link]() ==
[Link]());

false, then true. new String() always creates a new heap object — not from the pool. s1
== s2 compares two different references → false. intern() looks up or adds to the String
pool and returns the pool reference. Both [Link]() and [Link]() return THE SAME
pooled object → true.

Q: What happens? List<String> list = new ArrayList<>(); for(String s : list) {


if([Link]('remove')) [Link](s); }

ConcurrentModificationException! For-each loops use an Iterator internally. The


ArrayList iterator checks modCount (a modification count) on every call to next(). Calling
[Link]() increments modCount. Iterator detects the mismatch → throws CME. Fix:
use Iterator explicitly and call [Link](), or use [Link](s ->
[Link]('remove')).

Q: What is the output? class A { int x = 10; A() { print(); } void print() {
[Link](x); } } class B extends A { int x = 20; void print() {
[Link](x); } } new B();

0! When new B() is called, A's constructor runs first and calls print(). Due to dynamic
dispatch, B's print() is called (polymorphism). But at this point in construction, B's
instance variables haven't been initialized yet (B's constructor hasn't run). So x in B is
still the default value 0. This is the 'constructor calls overridden method' anti-pattern —
never call overridable methods from constructors.

Advanced Java Interview Mastery Handbook


Q: Is this code thread-safe? if (![Link](key)) { [Link](key, value); }

No! Even with ConcurrentHashMap, this check-then-act is a compound operation. Two


threads can both see containsKey() return false and both call put(). Use
[Link](key, value) which is atomic. Or [Link](key, k -> value).
These are single atomic operations in ConcurrentHashMap.

Q: What is wrong with this Comparator? comparator = (a, b) -> [Link]() -


[Link]();

Integer overflow! If [Link]() = Integer.MAX_VALUE (2,147,483,647) and


[Link]() = -1, the subtraction overflows to a negative number, making a appear
LESS than b when it should be greater. Always use [Link]([Link](),
[Link]()) for integer comparison. For doubles: [Link]([Link](),
[Link]()).

Q: What does this print? try { return 1; } finally { return 2; }

2. The finally block ALWAYS executes and its return OVERRIDES the try block's return.
This is one of the most subtle Java behaviors and a serious code smell — finally blocks
should almost never contain return statements. It silently swallows the original value.

Q: What is the time complexity of contains() on a HashSet?

O(1) average, O(n) worst case — but O(log n) since Java 8! After 8+ elements in a
bucket, the linked list converts to a red-black tree, making worst-case O(log n). The
common interview answer 'O(1)' is actually O(1) amortized average-case assuming good
hash distribution.

Q: Can you have a try block without catch OR finally?

No in Java 6 and earlier. Yes in Java 7+ with try-with-resources! try(Resource r = new


Resource()) { ... } — the finally (for closing) is implicit. The resource must implement
AutoCloseable. But a bare try{} block with no catch/finally/resources is a compile error.

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

Fail-fast (ArrayList, HashMap): Iterators throw ConcurrentModificationException if the


collection is structurally modified after iterator creation. Uses modCount internally. Fail-
safe (CopyOnWriteArrayList, ConcurrentHashMap): Iterate over a snapshot (copy-on-
write) or weakly consistent view. No CME, but may not reflect latest changes. The 'fail-
safe' term is informal — Java docs call it 'weakly consistent'.

Advanced Java Interview Mastery Handbook


9.2 System Design Micro-Questions

Q: How would you implement a thread-safe singleton counter that can handle 1
million increments per second?

AtomicLong is not enough at 1M/sec with high contention — all threads compete for one
CAS operation. Better: LongAdder (Java 8+). LongAdder maintains a Cell array — each
thread increments its own cell, dramatically reducing contention. sum() combines all
cells. Trade-off: sum() is not instantaneous — it's eventually consistent. For exact counts
at read time, AtomicLong is required. For approximate high-throughput counters,
LongAdder is 10-100x faster.

Q: Design a cache that automatically expires entries after N seconds.

Use a ConcurrentHashMap<K, CacheEntry> where CacheEntry holds the value and


expiry timestamp. On get(), check if [Link]() > [Link] — return
null and schedule removal. For cleanup: a ScheduledExecutorService running every
second to remove expired entries (ConcurrentHashMap allows concurrent iteration and
removal). Production: use Guava Cache (expireAfterWrite) or Caffeine — which uses an
O(1) optimal expiration policy (TinyLFU).

Q: What is the difference between optimistic and pessimistic locking? When


would you use each?

Pessimistic locking: Lock the resource before reading/writing. 'Assume conflict will
happen.' SELECT ... FOR UPDATE in SQL, synchronized in Java. High overhead,
prevents starvation. Best for: high-contention, short transactions, when conflicts are
frequent. Optimistic locking: No lock — detect conflict on write. Use version number
(SELECT ... + version, UPDATE ... WHERE version=N fails if N changed). CAS in
AtomicInteger, JPA @Version annotation. Low overhead when conflicts are rare. Best
for: low-contention reads, long transactions where you don't want to hold locks.

9.3 Common Mistakes That Eliminate Candidates

Mistake What Candidates Do What They Should Do


Float/Double equality if(a == b) for doubles if([Link](a-b) < 1e-9) or
BigDecimal for money
String concatenation in loops str += item in loop → O(n²) [Link]() in loop
→ O(n)
Mutable key in HashMap Change hashCode() after put() Never mutate an object used
as a HashMap key
Catching Exception broadly catch(Exception e) { log(e); } Catch specific exceptions,
don't swallow

Advanced Java Interview Mastery Handbook


Ignoring equals/hashCode Override equals() but not ALWAYS override both
contract hashCode() together
NullPointerException in [Link]() without [Link]() / orElseGet() /
Optional isPresent() orElseThrow()
Thread pool exhaustion Unbounded thread pool for Use non-blocking IO or
blocking IO bounded pool with queue

Advanced Java Interview Mastery Handbook


CHAPTER 10: Memory Optimization and Performance
Tuning
Senior engineers are expected to write not just correct code, but efficient code. This chapter covers
the techniques that separate high-performance Java applications from average ones.

10.1 Object Creation Optimization

// 1. Object Pooling: Reuse expensive objects


// Bad: creating new connection per request
Connection conn = [Link](url); // Expensive!

// Good: Connection pool (HikariCP, C3P0)


// HikariCP defaults: min 10 connections, max 10 connections
// Each connection checkout/return: ~microseconds vs ~milliseconds

// 2. Avoid autoboxing in hot paths


// Bad: causes Integer object creation on every iteration
List<Integer> nums = new ArrayList<>();
for (Integer i : nums) { total += i; } // Unboxing per element

// Good: Use int[] or IntStream for numeric processing


int[] arr = [Link]().mapToInt(Integer::intValue).toArray();

// 3. StringBuilder for string concatenation


// s1 + s2 + s3 creates 2 intermediate String objects
// [Link]().append() = 0 intermediate objects

10.2 JVM Tuning for Production

// Essential JVM flags for production Java applications:

// Memory settings:
// -Xms4g -Xmx4g (same min/max avoids heap resizing pauses)
// -XX:MaxMetaspaceSize=512m (prevent unconstrained metaspace growth)

// GC tuning:
// -XX:+UseG1GC (use G1GC explicitly)
// -XX:MaxGCPauseMillis=200 (target max pause 200ms)
// -XX:G1HeapRegionSize=8m (region size hint)
// -XX:+PrintGCDetails -Xloggc:[Link] (GC logging for analysis)

// JIT optimization:
// -XX:+TieredCompilation (default Java 8+)
// -XX:ReservedCodeCacheSize=256m (more code cache for large apps)

// Startup optimization:
// -XX:+UseStringDeduplication (with G1: deduplicate identical Strings)
// Java 11+: -XX:+UseContainerSupport (for Docker containers!)

Advanced Java Interview Mastery Handbook


🎯 Tricky Interview Question
'What happens when you run Java in Docker without -XX:+UseContainerSupport?' Before
Java 10, the JVM reads CPU and memory from the HOST, not the container. A container
limited to 512MB RAM on a 16GB host would get -Xmx4g by default (1/4 of host RAM),
causing OOMKilled. Since Java 10, +UseContainerSupport is default — JVM reads cgroup
limits and sets heap accordingly. Always verify with java -XX:+PrintFlagsFinal | grep
MaxHeapSize.

10.3 Profiling and Bottleneck Identification


Tool What It Measures Best For
JProfiler / YourKit CPU, memory, threads, live Development profiling
Java Flight Recorder (JFR) Low-overhead production Production performance
profiling analysis
VisualVM Heap dumps, thread analysis Memory leak investigation
async-profiler CPU flames, allocation Low-overhead sampling in
profiling prod
JMeter / Gatling Load testing, throughput Capacity planning
Micrometer + Prometheus Application metrics Ongoing monitoring

CHAPTER 11: Advanced Java Concepts — Expert Level

11.1 Java Generics: Type Erasure and Wildcards

// Type Erasure: generics are compile-time only


// At runtime, List<String> and List<Integer> are BOTH just List
// You cannot do: new T(), instanceof T, T[].class at runtime

// Wildcard variance:
// Covariant: ? extends T — Read-only (producer)
List<? extends Number> nums; // Can read as Number, cannot add
// Contravariant: ? super T — Write-only (consumer)
List<? super Integer> ints; // Can add Integer, cannot read (only Object)

// PECS mnemonic: Producer Extends, Consumer Super


// If you GET from it: ? extends T
// If you PUT into it: ? super T

// [Link]() example:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
// src PRODUCES T, dest CONSUMES T — PECS in action!
}

Advanced Java Interview Mastery Handbook


🎯 Tricky Interview Question
'Why can't you do List<Integer> list = new ArrayList<Number>()?' Because generics are
invariant. Even though Integer extends Number, List<Integer> is NOT a subtype of
List<Number>. If it were, you could: List<Number> nums = new ArrayList<Integer>();
[Link](3.14); // Adding Double to Integer list! Invariance prevents type corruption.
Solution: use wildcards (List<? extends Number>) for covariant read-only access.

11.2 Virtual Threads (Project Loom — Java 21)

// Traditional platform threads: 1 Java thread = 1 OS thread


// Memory: ~1MB per thread stack, OS thread creation is expensive
// For 10,000 concurrent connections = 10GB stack memory!

// Virtual Threads (Java 21): Lightweight user-mode threads


// Managed by JVM, not OS. Millions of virtual threads possible!
// ~100 bytes initial stack vs ~1MB for platform thread

// Creating virtual threads:


Thread vt = [Link]().start(() -> {
// This code runs on a virtual thread
callBlockingIO(); // Virtual thread 'parks' — carrier thread freed!
});

// ExecutorService with virtual threads:


try (ExecutorService executor = [Link]()) {
for (int i = 0; i < 1_000_000; i++) {
[Link](() -> handleRequest()); // 1M virtual threads!
}
}

// When virtual threads block on IO, the carrier thread is returned


// to the pool for other virtual threads. True M:N threading model!

💡 When NOT to Use Virtual Threads


Virtual threads are not faster for CPU-bound work — they still run on carrier threads
(physical CPU cores). They excel at IO-bound workloads where traditional threads would
block. For CPU-intensive tasks, traditional platform threads in a fixed pool = same or better
performance.

11.3 The HashMap equals/hashCode Contract

// The CONTRACT (must never be violated):


// 1. If [Link](b), then [Link]() == [Link]() (REQUIRED)
// 2. If [Link]() == [Link](), [Link](b) may or may not be true

// Violation consequences:
// HashMap uses hashCode to find bucket, then equals to confirm

Advanced Java Interview Mastery Handbook


// If equal objects have different hashCodes: HashMap stores DUPLICATES
// If unequal objects have same hashCode: collision, performance degrades

// Common mistake: only override equals for entity comparison


class User {
int id; String name;
@Override public boolean equals(Object o) {
return o instanceof User && ((User)o).id == [Link];
}
// forgot hashCode! Default hashCode is [Link]()
// Two 'equal' Users with id=1 have DIFFERENT hashCodes!
// Set<User> will contain duplicates!
}

// Correct:
@Override public int hashCode() {
return [Link](id); // MUST be consistent with equals
}

CHAPTER 12: Interview Strategy and Mental Framework


Technical knowledge alone doesn't win interviews. This chapter covers the meta-skills that
differentiate offer recipients from strong candidates who don't make it.

12.1 The STAR Framework for System Design


Phase Duration What to Do
Clarify 3-5 min Ask scope, scale, constraints,
non-functional requirements
High-Level Design 5-7 min Draw major components, data
flow, API contracts
Deep Dive 10-15 min Pick 2-3 critical components
for detailed design
Trade-offs 3-5 min Discuss alternatives you
considered and why you chose
this
Scale & Bottlenecks 3-5 min Identify failure points, how it
scales to 10x/100x load

💡 The Golden Rule


Never start coding or designing without clarifying requirements. The first 3 minutes of a
system design interview are the highest ROI minutes. Ask: What's the scale (users,
requests/sec, data size)? What are the SLAs (latency, availability)? Read-heavy or write-
heavy? Global or regional? Most candidates skip this — don't be most candidates.

Advanced Java Interview Mastery Handbook


12.2 Complexity Analysis Framework
When analyzing any algorithm, use this structured approach:

1. State the algorithm and its key operations


2. Identify the dominant operations (most expensive step)
3. Express as a function of input size n
4. Drop constants and lower-order terms → Big-O
5. State best case, average case, worst case separately
6. State space complexity (auxiliary + input)
7. Mention practical considerations (cache behavior, constant factors)

12.3 Top 10 Soft Skills Mistakes

Mistake Why It Hurts What to Do Instead


Coding in silence Interviewer can't follow, can't Narrate your thought process
help continuously
Jumping to code Shows lack of planning Clarify → Approach → Code
→ Test
Getting defensive on feedback Shows poor collaboration 'That's a good point, let me
reconsider...'
Saying 'I don't know' and Missed recovery opportunity 'I'm not certain, but my intuition
stopping is...'
Optimizing prematurely Missing correct brute-force first State O(n²) solution, then
optimize
Not testing your code Miss edge cases the Always trace through 2-3
interviewer noticed examples
Over-engineering the design Complexity without justification Start simple, justify each
complexity you add
Asking too many questions Appears unable to make Make reasonable
decisions assumptions, state them

12.4 Questions to Ask Your Interviewers


Asking thoughtful questions at the end signals intellectual engagement and helps you evaluate the
role. The best questions show you've thought deeply about engineering challenges:

• What are the largest technical challenges your team is facing in the next 6 months?
• How do you balance technical debt with new feature development?
• What does a typical on-call rotation look like for this team?
• How does the team approach performance optimization decisions?
• What's the testing philosophy here — TDD, BDD, or pragmatic testing?
• How do you handle disagreements on technical approach within the team?

Advanced Java Interview Mastery Handbook


APPENDIX: Quick Reference Cheat Sheet

A.1 Collection Complexity Summary


Collection Access Search Insert Delete Ordered?
ArrayList O(1) O(n) O(1)* O(n) Yes (index)
LinkedList O(n) O(n) O(1) O(1) Yes (node)
HashMap O(1)* O(1)* O(1)* O(1)* No
TreeMap O(log n) O(log n) O(log n) O(log n) Yes (sorted)
HashSet — O(1)* O(1)* O(1)* No
TreeSet — O(log n) O(log n) O(log n) Yes (sorted)
PriorityQueue O(1) min O(n) O(log n) O(log n) Heap-ordered
ArrayDeque O(1) O(n) O(1) O(1) Yes (deque)

* = amortized

A.2 Sorting Algorithm Reference


Algorithm Best Average Worst Space Stable?
Bubble Sort O(n) O(n²) O(n²) O(1) Yes
Insertion Sort O(n) O(n²) O(n²) O(1) Yes
Merge Sort O(n log n) O(n log n) O(n log n) O(n) Yes
Quick Sort O(n log n) O(n log n) O(n²) O(log n) No
Heap Sort O(n log n) O(n log n) O(n log n) O(1) No
TimSort O(n) O(n log n) O(n log n) O(n) Yes
Counting Sort O(n+k) O(n+k) O(n+k) O(k) Yes

A.3 Design Pattern Reference


Pattern Category Java Usage Use When
Singleton Creational Runtime, Spring beans One shared instance
needed
Factory Creational [Link]() Object creation logic
varies
Builder Creational StringBuilder, Complex object
[Link] construction

Advanced Java Interview Mastery Handbook


Prototype Creational [Link]() Copying expensive
objects
Adapter Structural [Link]() Interface
incompatibility
Decorator Structural I/O Streams, Adding behavior
[Link] dynamically
Proxy Structural Spring AOP, RMI Access control, lazy
loading
Observer Behavioral EventListener, RxJava One-to-many event
notification
Strategy Behavioral Comparator, Sort Swap algorithms at
algorithms runtime
Command Behavioral Runnable, Thread Encapsulate request
as object
Template Method Behavioral AbstractList, HttpServlet Define algorithm
skeleton
Chain of Behavioral Servlet Filters, Spring Handler chain for
Responsibility Interceptors request

─────────────────────────────────────────────────────
End of Advanced Java Interview Mastery Handbook
Mastery comes from understanding the WHY, not memorizing the WHAT.

Advanced Java Interview Mastery Handbook

You might also like