The Complete Java Interview Guide — 220+ Questions From Mid-Level to Staff Engineer Series Part 9
How to Use This Guide
The Complete Java Interview Guide — 220+ Questions From Mid-Level to Staff Engineer Series Part 9
How to Use This Guide
- 🟢 Mid-Level (2–4 YOE) — You should know these cold
- 🟡 Senior (5+ YOE) — Expected at senior interviews
- 🔴 Staff (8+ YOE) — Deep internals, trade-offs, “explain the why”
Jump to your section:
[Hyperlink will be added in few days for better readability and access]
- Core Java Fundamentals
- OOP & Design Principles
- Generics & Type System
- Collections Framework
- Streams & Functional Programming
- Exception Handling
- Concurrency & Multithreading
- Modern Java (8 → 21)
- Memory Management & GC
- JVM Internals
Memory Management & GC
Q184: JVM heap layout 🟡
┌─────────────────────────────────────────────────────┐
│ HEAP │
│ ┌─────────────────────┐ ┌──────────────────────┐ │
│ │ Young Gen │ │ Old Gen │ │
│ │ ┌──────┐ ┌───┐┌───┐ │ │ │ │
│ │ │ Eden │ │S0 ││S1 │ │ │ Promoted objects │ │
│ │ │ │ │ ││ │ │ │ (survived many GCs) │ │
│ │ └──────┘ └───┘└───┘ │ │ │ │
│ └─────────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌────────────────┐
│ Metaspace │ │ Thread Stacks│ │ Direct Memory │
│ (native mem) │ │ (per thread) │ │ (NIO buffers) │
└──────────────┘ └──────────────┘ └────────────────┘
- Eden: ALL new objects are born here (via TLAB — Thread-Local Allocation Buffer)
- Survivor S0/S1: objects surviving a minor GC move here (alternate between S0 and S1)
- Old Gen: objects surviving many minor GCs get promoted here
- Metaspace: class metadata (replaced PermGen in Java 8), native memory, grows dynamically
- Thread Stacks: each thread gets ~1MB (platform) or ~KB (virtual), stores frames + locals
- Direct Memory: off-heap buffers via
ByteBuffer.allocateDirect(), used by NIO
// Check memory at runtime:
Runtime rt = Runtime.getRuntime();
long heapMax = rt.maxMemory(); // -Xmx
long heapUsed = rt.totalMemory() - rt.freeMemory();
// Detailed via MXBeans:
ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
Q185: Minor vs Major vs Full GC 🟡
Minor GC (Young Gen):
- Triggers when Eden is full
- Copies live objects from Eden → Survivor (S0 or S1)
- Objects that survived enough cycles → promoted to Old Gen
- Fast: 5–50ms typically (only scans young, which is mostly garbage)
- After: Eden is empty, one Survivor has live objects
Major GC (Old Gen):
- Triggers when Old Gen is filling up
- Slower: 100ms-seconds (Old Gen is larger, more live objects)
- G1 does “mixed” collections (young + selected old regions)
Full GC (entire heap):
- Stop-the-world: ALL application threads paused
- Compacts entire heap (eliminates fragmentation)
- Triggers: Old Gen exhausted,
System.gc(), Metaspace expansion - Should be RARE in a healthy app — if frequent, you have a leak or undersized heap
# Monitor GC:
-Xlog:gc*:file=gc.log:time,uptime,level,tags
# Key log patterns:
# "Pause Young" = Minor GC
# "Pause Full" = Full GC (BAD if frequent)
# "Concurrent Cycle" = G1/ZGC concurrent work (GOOD - no pause)
Q186: G1 vs ZGC vs Shenandoah — detailed comparison 🟡
G1 (Garbage-First) — default since Java 9:
- Divides heap into equal-sized regions (not fixed young/old boundary)
- Collects regions with most garbage first (hence “Garbage-First”)
- Concurrent marking, but compaction requires stop-the-world
- Target pause: ~200ms (tunable via
-XX:MaxGCPauseMillis=200)
ZGC — production since Java 15:
- Sub-millisecond pauses regardless of heap size (tested up to 16TB)
- Concurrent compaction (moves objects while app runs)
- Uses colored pointers (GC metadata stored in unused pointer bits)
- Load barriers on every reference load (checks if pointer is stale)
- Java 21: Generational ZGC (
-XX:+ZGenerational) adds generational collection
Shenandoah — RedHat, similar goals to ZGC:
- Sub-10ms pauses
- Brooks pointers (forwarding pointer per object) instead of colored pointers
- Available in OpenJDK, not in Oracle JDK
# Production flags:
-XX:+UseZGC -XX:+ZGenerational # Best for latency (Java 21+)
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 # Balanced
-XX:+UseParallelGC # Max throughput (batch jobs)
Follow-up: How do you choose? If P99 latency matters (APIs, event processing) → ZGC. If throughput matters more (nightly batch jobs) → Parallel. If unsure → G1 (safe default).
Q187: Container-aware JVM flags 🟡
# MUST-HAVE for containers:
-XX:MaxRAMPercentage=75.0 # Heap = 75% of container memory
-XX:+ExitOnOutOfMemoryError # Die cleanly → K8s restarts
-XX:+UseContainerSupport # On by default since Java 10
# WHY not -Xmx?
# Container limit may change (scaling, different environments)
# MaxRAMPercentage auto-adjusts. -Xmx3g is fixed.
# WHY 75% not 90%?
# Remaining 25% needed for:
# - Metaspace: ~150-300MB for Spring Boot apps
# - Thread stacks: 1MB × thread count (200 threads = 200MB)
# - Direct memory: NIO buffers
# - Native GC data structures
# - Code cache (JIT compiled code): 48-240MB
# Example: 4GB container
# Heap: 3GB (75%)
# Metaspace: ~250MB
# Stacks: ~200MB (200 threads)
# Direct: ~100MB
# Code cache: ~100MB
# OS/overhead: ~150MB
Q188: How ZGC achieves sub-millisecond pauses 🔴
Traditional GC pauses for: root scanning + marking + compaction.
ZGC makes marking AND compaction concurrent:
1. PAUSE (< 1ms): Scan GC roots (thread stacks, static fields)
- Only scans roots, not entire heap
- Fixed cost regardless of heap size
2. CONCURRENT MARK: Traverse object graph while app runs
- Uses colored pointers to track mark state IN the pointer itself
- No separate mark bitmap needed
3. CONCURRENT RELOCATE: Move objects while app runs
- Copies objects to new locations
- Uses load barriers: every reference load checks if pointer is stale
- If stale → self-heal: update pointer to new location on-the-fly
4. PAUSE (< 1ms): Final sync - flip reference mapping
// What a load barrier looks like conceptually:
Object loadReference(Object* ref) {
Object obj = *ref;
if (isRelocated(obj)) { // Check colored pointer bits
obj = forwardingAddress(obj); // Get new location
*ref = obj; // Self-heal: update in-place
}
return obj;
}
// This runs on EVERY object reference load — ~4% throughput cost
Q189: Memory leaks — detection and common patterns 🟡
Common leak patterns:
// 1. ThreadLocal in thread pool (most common in Spring apps)
private static final ThreadLocal<List<Event>> buffer = new ThreadLocal<>();
void process() {
buffer.set(new ArrayList<>());
try { doWork(); }
finally { buffer.remove(); } // MUST remove! Thread is reused.
}
// 2. Unbounded cache
private final Map<String, byte[]> cache = new ConcurrentHashMap<>();
void store(String key, byte[] data) {
cache.put(key, data); // Never evicts! Grows until OOM.
}
// Fix: use Caffeine with size/time limits
Cache<String, byte[]> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(30))
.build();
// 3. Event listener never removed
class Subscriber {
Subscriber(EventBus bus) {
bus.register(this); // Strong reference held by bus forever
}
// No unregister on disposal → this object never GC'd
}
// 4. Static collection growing forever
class AuditLog {
static final List<String> entries = new ArrayList<>(); // Never cleaned!
}
// 5. Closeable not closed
void readData() {
InputStream is = new FileInputStream("data.bin"); // Leak if exception before close
// Fix: try-with-resources
}
Detection tools:
# Live monitoring:
jmap -histo:live <pid> # Class histogram (top memory consumers)
jcmd <pid> GC.heap_info # Heap summary
# Heap dump analysis:
jmap -dump:live,format=b,file=heap.hprof <pid>
# Open in Eclipse MAT:
# → "Leak Suspects" report (auto-detects top retainers)
# → "Dominator Tree" (what's holding the most memory)
# → "Path to GC Roots" (WHY something isn't collected)
# Auto-dump on OOM:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/
Q190: Escape analysis and scalar replacement 🔴
Escape analysis: JVM determines if an object “escapes” its creating method/thread.
Three escape levels:
- No escape: used only within the method → can be optimized away
- Arg escape: passed to another method but doesn’t outlive the call
- Global escape: stored in field, returned, or shared across threads → must be heap-allocated
Scalar replacement: if object doesn’t escape, JVM decomposes it into primitive fields on the stack:
// This allocation MAY be eliminated entirely:
public double distanceFromOrigin(double x, double y) {
Point p = new Point(x, y); // No escape — only used locally
return Math.sqrt(p.x * p.x + p.y * p.y);
}
// After optimization: equivalent to just using x and y directly on stack
// No heap allocation, no GC pressure
// This CANNOT be optimized (escapes via return):
public Point createPoint(double x, double y) {
return new Point(x, y); // Escapes → must be heap-allocated
}
// This CANNOT be optimized (escapes via field store):
public void register(Point p) {
this.points.add(p); // Escapes to field → heap
}
Impact on benchmarking:
@Benchmark
public void bad() {
Point p = new Point(1, 2); // JVM eliminates this entirely!
double d = p.x + p.y; // Dead code — result unused
}
@Benchmark
public double good(Blackhole bh) {
Point p = new Point(1, 2);
return p.x + p.y; // Returning prevents elimination
}
Q191: Weak, Soft, and Phantom references — detailed 🟡
// ═══ SOFT REFERENCE: collected only under memory pressure ═══
// Use for: memory-sensitive caches
SoftReference<LargeReport> ref = new SoftReference<>(generateReport());
LargeReport report = ref.get(); // null if GC collected it
if (report == null) {
report = generateReport(); // Regenerate
ref = new SoftReference<>(report);
}
// JVM keeps soft refs as long as memory allows. Under pressure → collected.
// ═══ WEAK REFERENCE: collected at next GC regardless of memory ═══
// Use for: metadata caches, canonicalization maps
WeakReference<Employee> ref = new WeakReference<>(employee);
// If no strong ref to employee exists → collected at next GC
// WeakHashMap: entries auto-removed when keys are weakly reachable
Map<Object, Metadata> cache = new WeakHashMap<>();
Object key = new Object();
cache.put(key, new Metadata());
key = null; // No strong ref to key
System.gc();
cache.size(); // 0 - entry removed
// ═══ PHANTOM REFERENCE: for cleanup (replacing finalize) ═══
// get() ALWAYS returns null. Used with ReferenceQueue.
ReferenceQueue<HeavyResource> queue = new ReferenceQueue<>();
PhantomReference<HeavyResource> phantom =
new PhantomReference<>(resource, queue);
// Cleanup thread:
Reference<?> ref = queue.poll(); // Non-null after resource is GC'd
if (ref != null) { releaseNativeMemory(); }
Q192: String pool internals 🟡
// String Constant Pool: lives in heap (since Java 7, was PermGen before)
String a = "hello"; // Goes to pool
String b = "hello"; // Same reference as a (pooled)
String c = new String("hello"); // NEW object on heap — NOT in pool
String d = c.intern(); // Looks up pool, returns pooled reference
a == b; // true (same pool ref)
a == c; // false (c is separate object)
a == d; // true (intern returns pool ref)
// String concatenation:
String s = "a" + "b"; // Compile-time constant → "ab" in pool
String x = variable + "b"; // Runtime → StringBuilder.append (Java 8)
// or invokedynamic (Java 9+)
String deduplication (G1/ZGC):
-XX:+UseStringDeduplication
# Doesn't deduplicate String objects — deduplicates backing char[]/byte[] arrays
# Two String objects with same content → share one underlying array
# Saves memory when many duplicate strings exist (XML parsing, log processing)
Q193: GC roots — complete list 🟡
Objects reachable from a GC root are ALIVE. Everything else is garbage.
GC Roots include:
- Local variables in all active thread stacks
- Active threads themselves (Thread objects)
- Static fields of loaded classes
- JNI references (native code holding Java object refs)
- Synchronized monitors (objects currently locked)
- JVM internal references (class objects, exception objects, classloaders)
// Common "leak" = object is unintentionally reachable from a root:
// Static field root:
class Cache {
static final Map<String, Object> data = new HashMap<>(); // ROOT
// Anything in this map is ALIVE forever
}
// Thread-stack root:
void longRunningMethod() {
LargeObject obj = new LargeObject(); // On stack = ROOT
processForHours(); // obj stays alive entire time even if not used after line 1
// Fix: set obj = null before the long operation if it's no longer needed
}
Q194: TLAB and object allocation cost 🔴
// TLAB = Thread-Local Allocation Buffer
// Each thread gets a chunk of Eden pre-allocated exclusively to it
// Allocation = increment a pointer (bump pointer) — NO synchronization!
// Cost of `new Object()`:
// 1. Check if TLAB has space (pointer comparison) - 1 instruction
// 2. Bump pointer forward by object size - 1 instruction
// 3. Zero memory (optional, JVM may skip) - few instructions
// Total: ~10 CPU instructions. EXTREMELY cheap.
// Implication:
// Don't pool small objects (POJOs, records, small arrays)
// Object pooling adds: synchronization, state reset, lifecycle management
// That overhead > allocation cost for objects < 1KB
// When pooling DOES help:
// - Objects expensive to CREATE (not allocate): DB connections, SSL contexts
// - Objects expensive to INITIALIZE: large buffers requiring OS calls
Q195: OutOfMemoryError variants 🟡
// 1. Java heap space — most common
// Cause: heap full, GC can't free enough
// Fix: increase -Xmx, fix memory leak, reduce object creation rate
java.lang.OutOfMemoryError: Java heap space
// 2. Metaspace - class metadata overflow
// Cause: too many classes loaded (classloader leak, dynamic proxies)
// Fix: -XX:MaxMetaspaceSize=512m, fix classloader leak
java.lang.OutOfMemoryError: Metaspace
// 3. GC overhead limit exceeded
// Cause: >98% of time spent in GC, recovering <2% of heap
// Means: heap is nearly full of live objects (leak or undersized)
java.lang.OutOfMemoryError: GC Overhead limit exceeded
// 4. Direct buffer memory
// Cause: ByteBuffer.allocateDirect() exceeded limit
// Fix: -XX:MaxDirectMemorySize=512m, close buffers properly
java.lang.OutOfMemoryError: Direct buffer memory
// 5. Unable to create native thread
// Cause: OS thread limit (ulimit -u) or no memory for stack
// Fix: increase ulimit, reduce thread count, use virtual threads
java.lang.OutOfMemoryError: unable to create native thread
Q196: Metaspace deep dive 🟡
// What lives in Metaspace:
// - Class metadata (field info, method info, constant pool)
// - Method bytecodes
// - Annotations
// - Static variables (reference pointers — actual objects on heap)
// Key properties:
// - Native memory (not limited by -Xmx)
// - Grows dynamically (no fixed size like old PermGen)
// - Freed when ClassLoader is GC'd (all classes loaded by it become unreachable)
// Common Metaspace leak:
// App server hot-reload: each redeploy creates new ClassLoader
// Old ClassLoader not GC'd because something holds a reference
// Classes accumulate → Metaspace grows → OOM
# Monitor:
jcmd <pid> VM.metaspace # Detailed metaspace stats
# Limits:
-XX:MetaspaceSize=256m # When to trigger first GC for metaspace
-XX:MaxMetaspaceSize=512m # Hard ceiling (default: unlimited - dangerous!)
Q197: JMH — proper benchmarking 🔴
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(2) // Separate JVM instances — avoids profile pollution
@State(Scope.Thread) // Each thread gets own state
public class PayrollBenchmark {
private Employee employee;
@Setup
public void setup() {
employee = createRealEmployee(); // Use realistic data
}
@Benchmark
public BigDecimal measureTaxCalc() {
return calculateTax(employee); // RETURN result - prevents dead code elimination
}
@Benchmark
public void measureWithBlackhole(Blackhole bh) {
bh.consume(calculateTax(employee)); // Alternative: consume to prevent DCE
}
}
Common pitfalls:
// PITFALL 1: Dead code elimination
@Benchmark
public void bad() { calculateTax(emp); } // Result unused → JVM may eliminate entirely
// PITFALL 2: Constant folding
@Benchmark
public int bad2() { return 2 * 3; } // Compiler folds to 6 → measures nothing
// PITFALL 3: Loop optimization
@Benchmark
public void bad3() {
for (int i = 0; i < 1000; i++) calc(emp); // JVM may hoist out of loop
}
// PITFALL 4: Not forking (profile pollution from previous benchmarks)
// ALWAYS @Fork(2) minimum
// PITFALL 5: Insufficient warmup (JIT hasn't compiled hot paths yet)
// Default 5 iterations is usually enough, but verify with -prof perfasm
Q198: Direct memory (off-heap) 🔴
// Heap buffer: backed by byte[], managed by GC
ByteBuffer heap = ByteBuffer.allocate(1024);
// GC handles lifecycle. Safe but slower for I/O (extra copy to kernel buffer).
// Direct buffer: off-heap, OS-managed memory
ByteBuffer direct = ByteBuffer.allocateDirect(1024);
// NO GC overhead. Faster I/O (zero-copy to kernel possible).
// But: expensive to allocate (~100x slower than heap buffer), must manage lifecycle.
// When to use direct:
// - Long-lived buffers reused across many I/O operations
// - Large buffers for NIO channels (network, file I/O)
// - When you need memory-mapped files (FileChannel.map)
// When NOT to use:
// - Short-lived, small buffers (allocation cost dominates)
// - You don't control the lifecycle (risk of leak)
// Monitoring:
-XX:MaxDirectMemorySize=256m // Limit. Default = -Xmx value.
// Leak risk:
// DirectByteBuffer is GC'd like any object. But the native memory
// is freed by a Cleaner WHEN the Java object is GC'd.
// If you allocate many direct buffers without triggering GC → OOM: Direct buffer memory
// Fix: explicitly clean or trigger System.gc() (or just use heap buffers for short-lived work)
Q199: GC tuning — key metrics and flags 🟡
# Enable detailed GC logging (Java 11+):
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10m
# Key metrics to monitor (via Prometheus/Grafana):
What to watch:
- GC pause time (P99) — must be < your latency SLA
- GC throughput — % of time NOT in GC. Target: >95% (>98% ideal)
- Heap occupancy after GC — if Old Gen stays high after Full GC → LEAK
- Promotion rate — bytes/sec from Young → Old. High = too many medium-lived objects
- Allocation rate — bytes/sec into Eden. Very high = too many objects created
# Common tuning:
-XX:MaxGCPauseMillis=100 # G1: target pause time (trades throughput)
-XX:InitiatingHeapOccupancyPercent=45 # G1: start concurrent marking earlier
-XX:G1HeapRegionSize=16m # Larger regions for large heaps
-XX:+AlwaysPreTouch # Pre-fault pages at startup (avoids latency spikes later)
Q200: G1 garbage collection process — detailed 🔴
Phase 1: Young-Only Collections
- Only collects Eden + Survivor regions
- Promotes survivors to Old regions
- Triggered when Eden is full
- Pause: ~10-50ms
Phase 2: Concurrent Marking (starts when IHOP threshold hit)
- IHOP = Initiating Heap Occupancy Percent (default ~45%)
- Scans entire heap concurrently (snapshot-at-the-beginning)
- Identifies which Old regions have the most garbage
- Runs while app continues (minimal pause for root scan)
Phase 3: Mixed Collections
- Collects Young regions + selected Old regions (most garbage first)
- "Garbage-First" = collect regions with highest garbage ratio
- Multiple mixed GCs until Old Gen is sufficiently cleared
- Pause: ~50-200ms (tunable)
Phase 4: Full GC (FALLBACK - should be rare)
- Triggers if mixed GCs can't keep up with promotion rate
- Stop-the-world, compacts entire heap
- Indicates: heap too small, leak, or IHOP set too late
# G1 heap structure:
# [E][E][E][S][O][O][H][O][E][S][O][O]...
# E=Eden, S=Survivor, O=Old, H=Humongous (objects > region/2)
# Regions are 1-32MB each (auto-sized, or -XX:G1HeapRegionSize)
Q201: Heap dump analysis — step by step 🟡
# Step 1: Get the dump
# Option A: On OOM (automatic)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dumps/
# Option B: Manual (live system)
jmap -dump:live,format=b,file=heap.hprof <pid>
# Option C: Via jcmd (preferred - safer)
jcmd <pid> GC.heap_dump /tmp/heap.hprof
Step 2: Open in Eclipse MAT
Key views:
- Leak Suspects Report — MAT auto-analyzes and suggests top leak candidates
- Dominator Tree — “what single object retains the most memory?” Shows retained heap per object.
- Histogram — class-level: “there are 5 million String objects consuming 400MB”
- Path to GC Roots — “WHY isn’t this collected?” Shows the reference chain from GC root to the leaking object.
- OQL (Object Query Language) — SQL-like queries:
SELECT * FROM java.lang.String WHERE toString().length() > 10000
Step 3: Common patterns you’ll find:
- Large
char[]arrays → String leak (check who holds the Strings) - Many
HashMap$Node→ unbounded map - Thread objects with large locals → ThreadLocal leak
- Multiple ClassLoader instances → hot-reload leak
Q202: Cleaner API — replacing finalize() 🔴
// finalize() problems:
// 1. Unpredictable timing — may never run
// 2. Resurrection possible — object can become reachable again in finalize()
// 3. GC overhead — finalized objects survive extra GC cycle
// 4. Single finalizer thread — one slow finalizer blocks all others
// 5. Deprecated for removal in Java 18+
// Cleaner API (Java 9+):
public class NativeResource implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// Cleaning action - MUST NOT reference the enclosing object!
// (otherwise it prevents GC of the object we're trying to clean)
private static class CleaningAction implements Runnable {
private long nativePointer;
CleaningAction(long ptr) { this.nativePointer = ptr; }
public void run() { freeNative(nativePointer); }
}
private final Cleaner.Cleanable cleanable;
NativeResource() {
long ptr = allocateNative();
cleanable = cleaner.register(this, new CleaningAction(ptr));
}
@Override
public void close() {
cleanable.clean(); // Deterministic cleanup (preferred path)
}
// If close() never called → Cleaner runs action when GC collects this object
// Safety net, not primary mechanism
}
Q203: ExitOnOutOfMemoryError in production 🟡
# Without either flag: JVM continues in broken state after OOM
# - Some threads dead, some alive
# - Half-processed data, inconsistent state
# - Monitoring shows "alive" but it's a zombie
# - Manual intervention needed
# With -XX:+ExitOnOutOfMemoryError:
# - JVM calls System.exit(1) immediately on OOM
# - Process exits cleanly
# - Kubernetes detects exit → restarts pod automatically
# - Fast recovery (seconds), no zombie state
# With -XX:+CrashOnOutOfMemoryError:
# - JVM generates core dump (hs_err_pid.log) then crashes
# - More info for debugging, but messier exit
# - Use if you need the crash dump for analysis
# ALWAYS use one of these in containers:
-XX:+ExitOnOutOfMemoryError # Recommended for K8s/ECS
-XX:+HeapDumpOnOutOfMemoryError # Also add this - dump before exit 메타데이터
- post_id
- b5985e2aaac6
- slug
- the-complete-java-interview-guide-220-questions-from-mid-level-to-staff-engineer-series-part-9-b5985e2aaac6
- url
- https://medium.com/@basukinath/the-complete-java-interview-guide-220-questions-from-mid-level-to-staff-engineer-series-part-9-b5985e2aaac6
- canonical_url
- https://medium.com/@basukinath/the-complete-java-interview-guide-220-questions-from-mid-level-to-staff-engineer-series-part-9-b5985e2aaac6
- author_url
- https://medium.com/@basukinath
- status
- ok
- fetched_at
- 2026-07-13 06:23:13