Skip to main content
Java advanced Lesson 47 of 58

Java Interview Preparation

Essential Java interview topics — JVM internals, memory model, garbage collection, collections deep dive, multithreading questions, and Java 8+ questions with answers.

This guide covers the most common Java interview topics — from JVM internals to collections internals to concurrency. Each section explains the concept first so you understand it deeply, then gives the kind of short, precise answer interviewers expect. Understanding the “why” behind each answer is what separates a good answer from a great one.

JVM Architecture

The JVM is what makes Java’s “write once, run anywhere” promise possible. The compiler produces bytecode — a platform-neutral format — and the JVM on each target machine translates that into native instructions. Understanding the JVM’s components helps you reason about performance, memory, and startup behaviour.

┌─────────────────────────────────────────┐
│              Java Source (.java)         │
│                    │ javac               │
│              Bytecode (.class)           │
│                    │                     │
│  ┌─────────────────▼──────────────────┐ │
│  │              JVM                   │ │
│  │  ┌──────────────────────────────┐  │ │
│  │  │     Class Loader Subsystem   │  │ │
│  │  └──────────────────────────────┘  │ │
│  │  ┌──────────────────────────────┐  │ │
│  │  │       Runtime Data Areas     │  │ │
│  │  │  Heap | Stack | Method Area  │  │ │
│  │  │  PC Register | Native Stack  │  │ │
│  │  └──────────────────────────────┘  │ │
│  │  ┌──────────────────────────────┐  │ │
│  │  │    Execution Engine          │  │ │
│  │  │  Interpreter | JIT Compiler  │  │ │
│  │  └──────────────────────────────┘  │ │
│  └────────────────────────────────────┘ │
└─────────────────────────────────────────┘

Q: What does the JIT compiler do? The JIT (Just-In-Time) compiler monitors which bytecode paths run frequently (“hot spots”), then compiles those paths to native machine code at runtime. First execution is interpreted (slow); subsequent calls use compiled native code (fast). This is why Java warms up slowly but achieves near-native speed in long-running server applications.

Q: What are the JVM memory areas?

  • Heap — all objects; garbage-collected; shared across all threads
  • Stack — per-thread; stores frames (local variables + operand stack); LIFO
  • Method Area / Metaspace — class metadata, static fields, bytecode
  • PC Register — per-thread; points to the currently executing instruction
  • Native Method Stack — for native (JNI) method calls

Garbage Collection

Garbage collection frees developers from manual memory management, but understanding how it works helps you avoid memory leaks and tune JVM performance. The key insight is that the GC can only reclaim objects that are unreachable — if any live reference points to an object, it stays in memory.

Q: How does Java garbage collection work?

The garbage collector reclaims objects that are no longer reachable from any GC root (stack variables, static fields, JNI references). Most GCs use a generational model based on the observation that most objects die young:

Young Generation          Old Generation (Tenured)
┌──────────────────┐      ┌──────────────────┐
│  Eden  │ S0 │ S1 │  →   │   Long-lived      │
│  (new  │    │    │      │   objects         │
│  alloc)│    │    │      │                   │
└──────────────────┘      └──────────────────┘
    Minor GC (fast)            Major/Full GC (slow)
  • New objects are allocated in Eden
  • Minor GC: survives go Eden → Survivor spaces → promoted to Old after N cycles
  • Major GC: collects the Old Generation — causes longer pauses because it processes more data

Q: What are the main GC algorithms in modern Java?

GCFlagCharacteristic
G1 GC-XX:+UseG1GCDefault since Java 9; balanced throughput/latency
ZGC-XX:+UseZGCSub-millisecond pauses; Java 15+ production ready
Shenandoah-XX:+UseShenandoahGCLow latency; Red Hat maintained
Serial-XX:+UseSerialGCSingle-threaded; small apps only

Q: What causes a memory leak in Java? A memory leak in Java means objects that are no longer needed are still reachable, so the GC cannot collect them. Common causes:

  • Static collections that grow without bound (e.g. a cache with no eviction)
  • Event listeners or callbacks registered but never deregistered
  • ThreadLocal variables not removed after the request ends
  • Inner class instances that hold an implicit reference to the outer class

Collections Internals

Understanding how the collections work internally helps you choose the right data structure and avoid subtle bugs like the broken HashMap key after mutating a field used in hashCode().

Q: How does HashMap work internally?

HashMap stores entries in an array of buckets. The key’s hashCode() determines which bucket an entry goes into. If multiple keys hash to the same bucket (a collision), they’re stored in a linked list within that bucket. Since Java 8, if a bucket exceeds 8 entries, the linked list becomes a balanced red-black tree for O(log n) lookup instead of O(n).

bucket[0] → null
bucket[1] → Entry("apple", 1) → Entry("grape", 5)  ← collision: both hash to bucket 1
bucket[2] → Entry("banana", 3)
...
bucket[n] → null

Default initial capacity: 16 buckets. Default load factor: 0.75 — the map resizes (doubles its capacity and rehashes all entries) when 75% full.

Q: Why must you override hashCode() when you override equals()?

The contract: if a.equals(b) is true, then a.hashCode() == b.hashCode() must also be true. HashMap uses hashCode to find the bucket and equals to confirm the key. If you override equals without hashCode, two “equal” objects can land in different buckets — map.get(key) returns null even though you put the key in. The reverse direction (same hash, not equal) is fine — that’s just a collision.

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

  • Fail-fast (ArrayList, HashMap): throw ConcurrentModificationException if the collection is modified during iteration. They track a modCount field and check it on each next() call. This catches bugs immediately rather than silently producing wrong results.
  • Fail-safe (CopyOnWriteArrayList, ConcurrentHashMap): iterate over a snapshot of the data taken at iterator creation time. No exception, but modifications made after the iterator was created may not be visible.

Q: When would you use LinkedList over ArrayList?

Rarely. LinkedList uses more memory (each node has two extra pointer fields) and is slower for random access — O(n) vs O(1) for ArrayList. The case for LinkedList is frequent insertion or deletion at the head, or when you need it as a Deque. For the vast majority of use cases, ArrayList is faster and more memory-efficient.

String Internals

Q: Why is String immutable? Three reasons that reinforce each other:

  1. String pool — immutable strings can be safely shared between callers. The JVM interns string literals so "hello" == "hello" is true.
  2. Thread safety — an immutable object can never be in an inconsistent state, so it’s safe to share across threads without synchronisation.
  3. HashMap keyshashCode() is computed once and cached; because the string can never change, a cached hash is always valid.

Q: What is String interning? String literals are automatically interned — stored in a shared pool and reused across the application. "hello" == "hello" is true. new String("hello") bypasses the pool and creates a new object on the heap. s.intern() returns the pool reference for any string, allowing reference equality for non-literal strings.

Multithreading

Q: What is the difference between process and thread? A process has its own memory space — completely isolated from other processes. Threads within the same process share the heap but each has its own stack. Thread creation is cheaper than process creation, and inter-thread communication is simpler (shared heap), but this sharing requires careful synchronisation to avoid data races.

Q: What is deadlock? How do you prevent it?

Deadlock: Thread A holds lock X and waits for lock Y. Thread B holds lock Y and waits for lock X. Both wait forever — neither can make progress.

Prevention strategies:

  1. Lock ordering — always acquire multiple locks in the same globally-defined order everywhere in the codebase
  2. tryLock with timeout — if you can’t acquire the lock within a timeout, release what you hold and retry
  3. Minimise lock scope — hold locks for the shortest time possible; avoid calling external code while holding a lock
  4. Use higher-level concurrency utilitiesjava.util.concurrent classes (BlockingQueue, Semaphore, CountDownLatch) are designed to avoid deadlock

Q: What is the volatile keyword?

volatile guarantees that reads and writes go directly to main memory rather than a CPU cache, ensuring visibility across threads. It does NOT prevent race conditions on compound operations like i++ (which is read-modify-write — three steps). Use it for simple flags that one thread writes and another reads:

private volatile boolean running = true;
// Thread A: running = false   — write goes to main memory immediately
// Thread B: while (running) { ... }  — always reads from main memory, sees the change

Q: What is the difference between Callable and Runnable?

RunnableCallable
Return valuevoidV (generic)
Checked exceptionscannot throwcan throw
Submit to ExecutorServiceexecute() or submit()submit() only
ReturnsnothingFuture<V>

Use Callable when you need the result of the concurrent operation. Use Runnable for fire-and-forget tasks.

Java 8 Questions

Q: What is a functional interface? An interface with exactly one abstract method. Lambdas implement functional interfaces — the compiler matches the lambda’s signature to the single abstract method. @FunctionalInterface annotation enforces this at compile time but is optional. Key examples: Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>.

Q: What is the difference between map() and flatMap()?

map() transforms each element one-to-one — one input, one output. flatMap() transforms each element into a stream and flattens all those streams into one — useful when each element produces zero or more results:

// Each line produces multiple words — flatMap flattens them into a single stream
List<String> words = List.of("hello world", "foo bar")
    .stream()
    .flatMap(line -> Arrays.stream(line.split(" ")))
    .collect(Collectors.toList());
// [hello, world, foo, bar]

Q: What is Optional and when should you use it?

Optional<T> is a container that explicitly represents the possibility of an absent value — a better-designed alternative to returning null. Use it as a return type when a method might produce no result. Don’t use it as a method parameter or a field — that adds complexity without benefit.

Q: What is method reference? Give examples.

String::toUpperCase        // instance method on each element in the stream
System.out::println        // instance method on a specific object (System.out)
Integer::parseInt          // static method reference
ArrayList::new             // constructor reference — creates a new ArrayList

Common Coding Interview Questions

Reverse a String

new StringBuilder(s).reverse().toString()

Check if a String is a Palindrome

public boolean isPalindrome(String s) {
    // Normalise first — lowercase and strip non-alphanumeric characters
    String clean = s.toLowerCase().replaceAll("[^a-z0-9]", "");
    int left = 0, right = clean.length() - 1;
    while (left < right) {
        if (clean.charAt(left++) != clean.charAt(right--)) return false;
    }
    return true;
}

Find Duplicates in an Array

public Set<Integer> findDuplicates(int[] arr) {
    Set<Integer> seen = new HashSet<>();
    Set<Integer> dups = new HashSet<>();
    // seen.add() returns false if the element was already in the set
    for (int n : arr) if (!seen.add(n)) dups.add(n);
    return dups;
}

Two Sum

public int[] twoSum(int[] nums, int target) {
    // Store each number and its index; on each iteration check if complement was seen before
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        if (map.containsKey(complement)) return new int[]{map.get(complement), i};
        map.put(nums[i], i);
    }
    return new int[]{};
}

FizzBuzz

// Check the combined condition first to avoid double-printing
for (int i = 1; i <= 100; i++) {
    if      (i % 15 == 0) System.out.println("FizzBuzz");
    else if (i % 3  == 0) System.out.println("Fizz");
    else if (i % 5  == 0) System.out.println("Buzz");
    else                  System.out.println(i);
}

Fibonacci (iterative — O(n) time, O(1) space)

public long fibonacci(int n) {
    if (n <= 1) return n;
    // Track only the previous two values — no array needed
    long a = 0, b = 1;
    for (int i = 2; i <= n; i++) { long c = a + b; a = b; b = c; }
    return b;
}

Frequently Asked Questions

What is the difference between the heap and the stack in Java?
The stack stores method call frames — local variables and references — and is per-thread, LIFO, and automatically managed. The heap stores all objects and is shared across all threads, managed by the garbage collector. A StackOverflowError means you've overflowed the stack (usually infinite recursion); an OutOfMemoryError means the heap is full.
What is the difference between == and equals() and hashCode()?
== compares references (memory addresses) for objects and values for primitives. equals() compares logical equality — override it to define what 'equal' means for your class. hashCode() returns an integer hash used by HashMap/HashSet. The contract: if a.equals(b) is true then a.hashCode() == b.hashCode() must also be true. Always override both together.
What is the difference between Comparable and Comparator?
Comparable defines the natural ordering of a class — implement it in the class itself via compareTo(). Comparator defines an external ordering — you pass it to sort methods. Use Comparable for the primary sort order (e.g. String sorts alphabetically). Use Comparator for alternate orderings (e.g. sort strings by length).