Free Handbook · Every example compiled & verified

Interview Questions

Sixty Java interview questions — 25 junior, 25 mid-level, 10 senior — with model answers, what each is really testing, a coding round and a take-home checklist.

0 / 142 lessons🔥 0 day streak
ShareXLinkedIn

Module 15 · what you'll be able to do

  • Answer the 25 junior questions on core Java, OOP, collections and exceptions that every first screen draws from
  • Explain HashMap internals, generics, streams, concurrency and the JVM at mid-level depth
  • Reason through senior design questions on Spring-style services, transactions, JPA and production incidents
  • Run a live coding round with a repeatable script, and hand in a take-home that reviewers approve
01

How to use this module

Java interviews follow a remarkably stable pattern: a screen on core language and OOP, a technical round on collections, generics, streams and concurrency, and — for experienced roles — a design conversation that assumes Spring Boot, a relational database and a production system. The questions below are grouped the same way.

  • Say the answer out loud before opening it. Recognising an answer when you read it is not the same as producing it under pressure.
  • Read the "what they are really testing" line. It tells you what the interviewer will follow up on, which is where most candidates lose points.
  • Back every claim with code you have run. The modules this draws on — Collections, Generics, Lambdas and Streams, Concurrency — have examples you can paste into your editor.
Version matters in Java interviews
Many teams still run Java 17, most new work targets Java 21, and some interviewers learned Java 8. Say which version a feature arrived in ("records, since Java 16") — it shows you know the language is moving and avoids arguments about whether something exists.
02

Junior: core language — 10 questions

Asked in nearly every first-round Java screen, often in the first ten minutes. They are short questions with precise answers, and a vague answer here ends the interview early.

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

The JVM is the virtual machine that loads bytecode (.class files), verifies it, interprets it and JIT-compiles the hot parts to machine code. The JRE is the JVM plus the standard class library — everything needed to run a Java program. The JDK is the JRE plus the development tools: javac, jar, jshell, jdb, jcmd. Since Java 11 vendors ship the JDK only; a slim runtime is built with jlink when you need one.

What they are really testing: Whether they know which piece compiles, which piece runs, and that "install Java" for a developer means the JDK.

JuniorWhy is Java called "write once, run anywhere"?

javac does not compile to machine code for one CPU; it compiles to bytecode, an instruction set for the JVM. Any operating system with a JVM can run the same .class or .jar file unchanged. The platform-specific part is the JVM itself, which is written once per OS and CPU by the vendor.

What they are really testing: That they understand bytecode as the portable layer, not "Java runs everywhere by magic".

JuniorWhat is the difference between a primitive and its wrapper class, and what is autoboxing?

Java has eight primitives (int, long, double, boolean, char, byte, short, float) that hold a raw value and cannot be null. Each has a wrapper object (Integer, Long…) that can be null and can live in collections, because generics only accept reference types. Autoboxing is the compiler inserting Integer.valueOf(x) and x.intValue() for you. The traps: unboxing a null Integer throws NullPointerException, and boxing in a hot loop creates garbage.

What they are really testing: Whether they know generics force boxing, and that auto-unboxing is a hidden place for a NullPointerException.

JuniorWhat is the difference between == and .equals()?

== on primitives compares values; on references it compares identity — are these the same object in memory. .equals() compares logical equality as the class defines it; String, the wrappers and records all override it to compare contents. The classic bug is s1 == s2 on two strings that happen to be equal but were built separately. A second one: Integer a = 127, b = 127; a == b is true because of the Integer cache (-128 to 127), while the same with 128 is false.

What they are really testing: The single most common Java bug in code review. The Integer cache detail separates people who have been bitten from people who read about it.

JuniorWhy is String immutable, and what is the string pool?

Once created, a String’s characters never change; methods like toUpperCase() return a new string. That makes strings safe to share between threads without locking, safe as HashMap keys (the hash never changes, so it can be cached), and safe to pass to security-sensitive code (a file path cannot be altered after it is checked). The string pool is a table of string literals the JVM reuses: two identical literals in source code point at one object. new String("x") deliberately bypasses it.

What they are really testing: Whether they can give real reasons — thread safety, hash caching, security — rather than "because it is final".

JuniorWhen do you use String, StringBuilder or StringBuffer?

Use String for values that do not change. Building a string in a loop with += creates a new object every iteration, so use StringBuilder — a mutable buffer with append. StringBuffer is the old, synchronized version; its locking buys nothing in normal code because a builder is almost never shared between threads, so it is effectively legacy. A single-line concatenation like "id=" + id is fine — the compiler already optimises it.

What they are really testing: Knowing why += in a loop is slow, and not recommending StringBuffer out of habit.

JuniorIs Java pass-by-value or pass-by-reference?

Always pass-by-value. For primitives the value is the number itself. For objects the value that is copied is the reference. So a method can mutate the object the caller passed (both copies of the reference point at it), but reassigning the parameter to a new object inside the method has no effect on the caller’s variable.

static void reset(List<String> xs) {
    xs.clear();              // caller sees this
    xs = new ArrayList<>();  // caller does NOT see this
}

What they are really testing: A precision question. "Objects are passed by reference" is the wrong answer they are listening for.

JuniorWhat is the difference between final, finally and finalize?

final is a modifier: a final variable cannot be reassigned, a final method cannot be overridden, a final class cannot be extended. finally is the block after try that runs whether or not an exception was thrown — used for cleanup, though try-with-resources is usually better. finalize() was a method the garbage collector might call before reclaiming an object; it was unreliable, is deprecated for removal, and should never be used — use try-with-resources or java.lang.ref.Cleaner instead.

What they are really testing: Mostly a vocabulary check; saying finalize is deprecated shows current knowledge.

JuniorWhat does static mean?

A static field or method belongs to the class, not to any object. There is one copy of a static field shared by every instance, and a static method is called on the class (Math.max) and has no this, so it cannot read instance fields directly. Static is right for pure helpers and constants (static final); overused, it becomes global mutable state that is hard to test.

What they are really testing: Whether they understand "no this", and can say why static mutable state is a design smell.

JuniorExplain every word in public static void main(String[] args).

public — the JVM must be able to call it from outside the class. static — it runs before any object exists. void — it returns nothing; exit codes go through System.exit. main — the name the launcher looks for. String[] args — the command-line arguments. Since Java 25, small programs may also declare an instance void main() with no parameters, and the launcher creates the object for you — but the classic signature is what every existing codebase and interviewer expects.

What they are really testing: Basic fluency — and whether they know what the JVM needs, not just that it is boilerplate.

03

Junior: object-oriented Java — 8 questions

Java is object-oriented to the core, so interviewers probe whether you use classes as a design tool or only as a place to put methods. Examples win over definitions.

JuniorWhat are the four pillars of object-oriented programming, with a Java example of each?

Encapsulation — private fields behind methods, so a BankAccount balance can only change through deposit. Inheritance — class Savings extends BankAccount reuses and extends behaviour. Polymorphism — a List<Shape> calls area() and each subclass’s override runs. Abstraction — code depends on the List interface, not on how ArrayList stores things.

What they are really testing: Not the definitions — the examples. Candidates who can only recite the words have not used them.

JuniorWhat is the difference between method overloading and overriding?

Overloading: same name, different parameter lists, in the same class; the compiler picks one at compile time from the argument types. Overriding: a subclass supplies a new body for an inherited method with the same signature; the JVM picks one at run time from the object’s actual class (dynamic dispatch). Always mark overrides with @Override so a typo in the signature becomes a compile error instead of a silent overload.

What they are really testing: Compile-time vs run-time selection, and the practical value of @Override.

JuniorAbstract class or interface — how do you choose?

An interface describes a capability (Comparable, Runnable); a class can implement many, and since Java 8 interfaces can carry default and static methods, but no instance state. An abstract class can hold fields and constructors and share implementation, but a class can extend only one. Default to an interface; reach for an abstract class when subclasses genuinely share state or a template-method skeleton.

What they are really testing: Whether their answer is current (default methods exist) and whether they have a rule, not just a list of differences.

JuniorWhat do this() and super() do in a constructor?

this(...) calls another constructor of the same class, so defaults live in one place. super(...) calls the parent constructor. Either must be the first statement (Java 25 relaxes this to allow statements that do not touch this before it). If you write neither, the compiler inserts super() — which fails to compile if the parent has no no-arg constructor.

What they are really testing: Constructor chaining and the implicit super() call, a frequent source of confusing compile errors.

JuniorExplain Java’s four access levels.

private — the class only. package-private (no keyword) — any class in the same package. protected — the package plus subclasses in other packages. public — everyone. Good practice is the narrowest level that works: private fields, a small public surface, package-private for helpers that tests in the same package need.

What they are really testing: That they know the no-keyword level exists and what "protected" really includes.

JuniorCan you override a static method?

No. A static method with the same signature in a subclass hides the parent one; it does not override it. Which one runs depends on the declared type at compile time, not the object at run time, so polymorphism does not apply. @Override on it is a compile error, which is a good way to find out.

What they are really testing: Whether they connect overriding to dynamic dispatch, which needs an object.

JuniorWhat is a record, and when would you use one?

A record (Java 16) is a class for immutable data: record Point(int x, int y) {} generates private final fields, a constructor, accessors x() and y(), and equals, hashCode and toString based on the components. Use it for DTOs, API responses, map keys and value objects. It cannot extend another class, and validation goes in a compact constructor.

What they are really testing: Modern Java knowledge. Many candidates still hand-write fifty lines for a value object.

JuniorWhich methods does every class inherit from Object, and which do you usually override?

equals, hashCode, toString, getClass, clone, wait/notify/notifyAll (and the deprecated finalize). You override toString for readable logs, and equals and hashCode together whenever two objects should count as equal by value — otherwise they misbehave as HashMap keys or in a HashSet.

What they are really testing: The "together" — overriding equals without hashCode is the mistake they want you to name.

04

Junior: collections and exceptions — 7 questions

These separate someone who has written Java programs from someone who has read about Java: choosing the right collection and handling failure cleanly are daily work.

JuniorArrayList or LinkedList?

ArrayList almost always. It is backed by an array: get(i) is O(1), appending is amortised O(1), and iterating is cache-friendly. LinkedList is O(n) for get(i), and its cheap insert-in-the-middle only helps if you already hold an iterator at that spot; each node is a separate object, so it uses more memory and iterates slower. For a queue or stack, use ArrayDeque, not LinkedList.

What they are really testing: Whether they know the textbook "LinkedList is faster for inserts" answer is misleading in practice.

JuniorWhen do you use a List, a Set and a Map?

List — ordered by position, duplicates allowed (a cart). Set — uniqueness and fast "have I seen this?" checks (visited IDs). Map — look up a value by a key (user by email). Declare the variable as the interface (Map<String, User> users = new HashMap<>()) so the implementation can change in one place.

What they are really testing: Picking by the question the code asks, and coding to the interface.

JuniorWhat is the difference between HashMap, LinkedHashMap and TreeMap?

HashMap — O(1) average operations, no order guaranteed. LinkedHashMap — same speed, keeps insertion order (or access order, which makes a simple LRU cache). TreeMap — a red-black tree, O(log n), keys kept sorted, with range methods like floorKey and headMap. Printing a HashMap and expecting a stable order is a bug waiting to happen.

What they are really testing: That iteration order is a property you choose, not an accident.

JuniorWhat is the difference between checked and unchecked exceptions?

Checked exceptions extend Exception but not RuntimeException (IOException, SQLException); the compiler forces you to catch them or declare throws. They model recoverable conditions outside the program’s control. Unchecked exceptions extend RuntimeException (NullPointerException, IllegalArgumentException) and usually signal a bug. Error (OutOfMemoryError) is for JVM-level problems you should not catch.

What they are really testing: The hierarchy, and a sensible opinion on what each is for.

JuniorWhat is try-with-resources?

A try (var in = new FileInputStream(path)) { ... } form that automatically calls close() on anything implementing AutoCloseable when the block exits, in reverse order of opening, even if an exception is thrown. If close() itself throws, that exception is attached to the original as a suppressed exception rather than hiding it. It replaces the error-prone finally { if (in != null) in.close(); } pattern.

What they are really testing: Resource hygiene. Leaked file handles and connections are real production incidents.

JuniorArray or ArrayList?

An array has a fixed length, can hold primitives directly (int[]), and is slightly faster. An ArrayList grows as needed, has rich methods (contains, remove, sort via List.sort), but holds only objects, so List<Integer> boxes every number. Use arrays for fixed-size numeric work and method varargs; use lists everywhere else.

What they are really testing: Trade-offs, including boxing — not "arrays are old".

JuniorHow do you avoid NullPointerException?

Validate at the boundary (Objects.requireNonNull(arg, "arg") in constructors), return empty collections instead of null, return Optional from methods where "no result" is normal, call "literal".equals(x) rather than x.equals("literal"), and use Map.getOrDefault. When one does happen, read the helpful message the JVM prints (Java 14+): it names exactly which expression was null.

What they are really testing: Defensive habits, and whether they read the modern helpful-NPE message instead of guessing.

05

Mid-level: collections internals, generics and streams — 9 questions

For roles with two to five years of experience. The interviewer wants to know how the tools work inside, because that is what lets you predict their failure modes.

Mid-levelHow does HashMap work internally?

It is an array of buckets. put(k, v) calls k.hashCode(), mixes the high bits into the low bits (h ^ (h >>> 16)), and uses hash & (capacity - 1) to pick a bucket. Keys that land in the same bucket are chained; equals decides which entry matches. When a bucket holds more than 8 entries (and the table has at least 64 buckets) the chain becomes a red-black tree, so a bad hash degrades to O(log n), not O(n). When size exceeds capacity × load factor (default 16 × 0.75) the table doubles and entries are redistributed.

What they are really testing: The standard mid-level filter. They want buckets, hashCode then equals, resize, and treeification — in that order.

Mid-levelWhat is the equals/hashCode contract, and what breaks if you violate it?

If a.equals(b) then a.hashCode() == b.hashCode(). (The reverse is not required — collisions are allowed.) Override equals without hashCode and two "equal" keys land in different buckets: set.contains(copy) returns false and a HashSet holds duplicates. A second trap: using a mutable object as a key and changing a field that feeds hashCode after insertion — the entry is now in the wrong bucket and effectively lost. Records and immutable keys avoid both.

What they are really testing: Whether they can predict the actual symptom, not just recite the rule.

Mid-levelWhat is the difference between Comparable and Comparator?

Comparable<T> is implemented by the class itself (compareTo) and defines its one natural order — String alphabetical, Integer numeric. Comparator<T> is a separate object for any other order, and composes: Comparator.comparing(Employee::dept).thenComparing(Employee::salary, Comparator.reverseOrder()). Never compare with a - b; it overflows for large ints. Use Integer.compare.

What they are really testing: Fluency with the Comparator combinators, and the subtraction-overflow bug.

Mid-levelWhat does "fail-fast iterator" mean, and how do you remove items while iterating?

Collections in java.util keep a modification count; if the collection is structurally changed during iteration by anything other than the iterator itself, the next next() throws ConcurrentModificationException. It is a best-effort bug detector, not a thread-safety guarantee. To remove safely use iterator.remove() or, simpler, list.removeIf(x -> x.isExpired()). Concurrent collections like CopyOnWriteArrayList and ConcurrentHashMap have weakly consistent iterators that never throw it.

What they are really testing: Whether they know CME usually happens in single-threaded code, and know removeIf.

Mid-levelWhat is type erasure, and what can you not do because of it?

Generics are checked at compile time and then erased: at run time a List<String> and a List<Integer> are both just List. Consequences: no new T(), no new T[n], no instanceof List<String>, no overloading m(List<String>) with m(List<Integer>), and no primitives as type arguments. The usual workaround is passing a Class<T> or a Supplier<T>.

What they are really testing: Understanding generics as a compile-time feature, which explains most of their odd restrictions.

Mid-levelExplain ? extends T versus ? super T (PECS).

Producer Extends, Consumer Super. A parameter you only read from should be List<? extends Number> — it accepts a List<Integer> or List<Double>, but you cannot add to it. A parameter you only write into should be List<? super Integer> — it accepts a List<Number> or List<Object>. Collections.copy(List<? super T> dest, List<? extends T> src) is the textbook signature.

What they are really testing: Whether they can design a flexible generic API rather than only consume one.

Mid-levelWhat is the difference between intermediate and terminal stream operations?

Intermediate operations (filter, map, sorted, limit) return a new stream and are lazy — nothing runs until a terminal operation (collect, toList, forEach, count, findFirst) pulls elements through. Laziness lets limit and findFirst short-circuit. A stream can be consumed once; a second terminal call throws IllegalStateException.

What they are really testing: That streams are a pipeline, not a collection — and the single-use rule.

Mid-levelWhat is the difference between map and flatMap?

map turns each element into exactly one element: Stream<Order> to Stream<Customer>. flatMap turns each element into a stream of zero or more elements and concatenates them: orders.stream().flatMap(o -> o.lines().stream()) gives every line of every order as one flat stream. The same idea exists on Optional, where it avoids Optional<Optional<T>>.

What they are really testing: A concrete one-to-many example, which shows real stream use.

Mid-levelHow do you group and count with streams, and what goes wrong with Collectors.toMap?

Collectors.groupingBy(Employee::dept, Collectors.counting()) gives a Map<String, Long>; the downstream collector can be summingInt, mapping, toSet and so on. toMap(k, v) throws IllegalStateException: Duplicate key the moment two elements produce the same key — pass a merge function, toMap(k, v, Integer::sum), and a map supplier such as TreeMap::new if order matters.

What they are really testing: Real-world stream use plus the duplicate-key exception that hits everyone once in production.

06

Mid-level: class design and exceptions — 6 questions

Questions about how you shape classes and APIs other people will call — immutability, Optional, modern sealed types and exception discipline.

Mid-levelHow do you write an immutable class?

Make the class final (or use a record), all fields private final, no setters, and defensive copies of any mutable input and output: this.tags = List.copyOf(tags) in the constructor. Return new objects from "modifying" methods (withPrice(...)). A record with a List component is not deeply immutable unless you copy the list in a compact constructor.

What they are really testing: The defensive-copy step. "final fields" alone leaves a mutable list exposed.

Mid-levelHow should Optional be used, and how is it misused?

It is a return type for "there may be no result": Optional<User> findByEmail(...). Callers use map, orElse, orElseThrow, ifPresent. Misuses: calling get() without checking (a slower NullPointerException), using it as a field or method parameter, putting it in collections, and returning null from a method declared to return Optional. For primitives use OptionalInt.

What they are really testing: Whether they treat Optional as API design, not as a null replacement sprinkled everywhere.

Mid-levelWhat are your rules for handling exceptions well?

Catch only what you can handle; let the rest propagate to one place (a controller advice, the top of a job). Never swallow an exception with an empty catch. When translating, keep the cause: throw new OrderException("order " + id, e). Catch specific types, not Exception. Log once, at the place that handles it — not at every layer. Do not use exceptions for normal control flow; they are expensive because they capture a stack trace.

What they are really testing: Production maturity. Lost causes and log-and-rethrow at every layer are what reviewers flag.

Mid-levelWhat are sealed classes, and how do they work with pattern matching?

sealed interface Shape permits Circle, Square, Rect {} (Java 17) restricts which classes may implement it. Combined with records and a pattern-matching switch (Java 21), the compiler knows every case, so no default is needed and adding a new subtype turns every incomplete switch into a compile error:

double area(Shape s) {
    return switch (s) {
        case Circle c -> Math.PI * c.r() * c.r();
        case Square q -> q.side() * q.side();
        case Rect r   -> r.w() * r.h();
    };
}

What they are really testing: Whether they are writing Java 21 or Java 8. Exhaustiveness checking is the point to mention.

Mid-levelWhat happens if a finally block contains a return?

It wins. A return in finally overrides the value returned by try and silently discards any exception thrown in try or catch. That is why it is a warning in every linter: never return or throw from finally. Use it only for cleanup, or better, use try-with-resources.

What they are really testing: A classic trick question; the real test is knowing the exception disappears.

Mid-levelWhat is a functional interface, and why must captured variables be effectively final?

An interface with exactly one abstract method (Runnable, Function<T,R>, Predicate<T>, Supplier<T>); a lambda or method reference can implement it. @FunctionalInterface makes the compiler enforce the single method. A lambda captures the value of a local variable, not the variable, and it may run later or on another thread; allowing the local to change afterwards would make that copy silently stale, so Java requires it to be effectively final.

What they are really testing: Understanding lambdas as objects implementing an interface, and the reason behind the capture rule.

07

Mid-level: concurrency — 5 questions

Almost every Java backend is multi-threaded, whether or not the code says so — a web server handles requests on many threads at once. These questions check that you know what is and is not safe.

Mid-levelWhat is the difference between synchronized and ReentrantLock?

Both give mutual exclusion and memory visibility, and both are reentrant. synchronized is a language keyword: the lock is released automatically, even on an exception. ReentrantLock is a class with extra features — tryLock() with a timeout, interruptible waits, an optional fairness policy and multiple Conditions — but you must unlock() in a finally. Default to synchronized or a higher-level utility; use a lock when you need one of those features.

What they are really testing: That they know the trade-off, and the unlock-in-finally discipline.

Mid-levelWhat does volatile guarantee, and what does it not?

A volatile field guarantees visibility and ordering: a write by one thread is seen by every later read in another thread, and it is never cached in a register. It does not make compound operations atomic — count++ on a volatile int is still a read, an add and a write, and updates are lost under contention. Use it for a stop flag; use AtomicInteger/LongAdder or a lock for counters.

What they are really testing: The visibility vs atomicity distinction — the core of the Java memory model at interview depth.

Mid-levelWhy use an ExecutorService instead of new Thread()?

A platform thread is expensive to create and unbounded creation can exhaust memory. An executor reuses a pool of threads, queues tasks, returns Futures for results and exceptions, and has a lifecycle (shutdown, awaitTermination, or try-with-resources since Java 19). For I/O-bound work in Java 21+, Executors.newVirtualThreadPerTaskExecutor() gives one cheap virtual thread per task. For composition, CompletableFuture builds pipelines on top of an executor.

What they are really testing: Knowing why pooling exists and that uncaught exceptions in a Future are only seen when you call get().

Mid-levelHow is ConcurrentHashMap different from Collections.synchronizedMap?

synchronizedMap wraps every method in one lock, so all threads queue behind each other. ConcurrentHashMap locks per bucket on writes and does not lock for reads, so it scales. It also offers atomic compound operations — computeIfAbsent, merge, putIfAbsent — which matter because if (!map.containsKey(k)) map.put(k, v) is a race on any map. It does not allow null keys or values.

What they are really testing: That thread-safe individual methods do not make check-then-act sequences safe.

Mid-levelWhat are virtual threads, and when do they help?

Virtual threads (final in Java 21) are threads managed by the JVM rather than the OS. They are cheap enough to create one per request — millions if needed. When a virtual thread blocks on I/O it unmounts from its carrier OS thread, which picks up other work. They help I/O-bound code written in plain blocking style (HTTP calls, JDBC). They do not make CPU-bound work faster, and you should not pool them. Pinning inside synchronized blocks was a known limitation, largely removed in Java 24.

What they are really testing: Whether they know the one thing virtual threads are for, and do not oversell them.

08

Mid-level: the JVM, memory and GC — 5 questions

You do not need to tune a garbage collector to get a mid-level job, but you need a working model of where objects live, how they die and why the JVM speeds up after start-up.

Mid-levelWhat lives on the stack and what lives on the heap?

Each thread has its own stack of frames: local primitives, references and return addresses for method calls in progress; it is freed automatically when a method returns. Objects live on the shared heap and are reclaimed by the garbage collector when unreachable. Deep recursion overflows the stack (StackOverflowError); too many live objects exhaust the heap (OutOfMemoryError: Java heap space). The JIT’s escape analysis can avoid heap allocation for objects that never leave a method.

What they are really testing: Mapping the two errors to the two memory areas is what they want.

Mid-levelHow does garbage collection work in the JVM?

The GC finds objects reachable from roots (thread stacks, static fields, JNI handles); everything else is garbage, cycles included. Heaps are generational because most objects die young: new objects go to the young generation, which is collected often and cheaply; survivors are promoted to the old generation. G1 (the default) splits the heap into regions and aims for a pause-time target; ZGC does almost all work concurrently for very short pauses. You tune with heap size (-Xmx) first and GC flags last.

What they are really testing: Reachability (not reference counting), the generational hypothesis, and knowing the default collector.

Mid-levelHow can a Java program leak memory if there is a garbage collector?

The GC only frees unreachable objects; a leak is objects that are still reachable but no longer needed. Common causes: a static Map used as a cache with no eviction, listeners registered and never removed, ThreadLocal values in pooled threads that are never cleared, and keys whose hashCode changes so entries can never be removed. You find them with a heap dump (jcmd <pid> GC.heap_dump) and a dominator-tree view in a tool such as Eclipse MAT.

What they are really testing: Whether they understand "reachable but useless", and know how to get a heap dump.

Mid-levelHow does class loading work?

Classes are loaded lazily, on first use, by a hierarchy of class loaders: bootstrap (core java.*), platform, then the application class loader for your classpath. Loaders delegate to the parent first, so your code cannot replace java.lang.String. Loading is followed by linking (verification, preparation) and initialisation, when static initialisers run — exactly once. ClassNotFoundException means a class looked up by name was missing; NoClassDefFoundError means it was present at compile time but missing or failed to initialise at run time.

What they are really testing: Parent delegation, and the CNFE vs NCDFE distinction that shows up in real dependency conflicts.

Mid-levelWhat does the JIT compiler do, and why does Java get faster after it starts?

The JVM starts by interpreting bytecode while counting how often each method and loop runs. Hot code is compiled to native machine code — first quickly by C1, then aggressively by C2 using real profiling data: inlining, escape analysis, removing bounds checks, speculative optimisations it can undo if an assumption breaks. That is why a service is slower for its first seconds (warm-up) and why micro-benchmarks need a harness like JMH rather than a timing loop.

What they are really testing: Whether they understand warm-up, and why naive benchmarks of Java code lie.

09

Senior: service design and production — 10 questions

Senior Java interviews assume the ecosystem around the language: Spring Boot, JPA/Hibernate, a relational database and a system running in production. There is no single right answer; the model answers show the shape of a strong one — what you would ask first, what you would measure, and which trade-off you would accept.

SeniorDesign a REST service for placing orders. How do you structure it?

Layers with one job each: a controller that maps HTTP to a command and validates the request shape (@Valid on a record DTO); a service that holds the business rules and owns the transaction boundary; a repository for persistence. DTOs at the edge, domain objects inside — never serialise JPA entities straight to JSON. Errors mapped once in a @RestControllerAdvice to consistent problem responses. Then the non-functional questions I would raise before coding: expected load, idempotency of POST /orders, what happens when payment is down, how the schema is migrated (Flyway), and what is logged and measured.

What they are really testing: Whether they design from responsibilities and failure modes, and ask questions before drawing boxes.

SeniorWhy prefer constructor injection over field injection?

Constructor injection makes dependencies explicit and required: fields can be final, the object is never half-built, and a unit test can construct it with plain new and fakes — no container needed. Field injection (@Autowired on a private field) hides dependencies, allows nulls in tests, and makes a class with twelve dependencies look innocent. A constructor with too many parameters is useful feedback: the class does too much.

What they are really testing: Testability and design feedback, not framework trivia.

SeniorYour @Transactional method is not rolling back. What could be wrong?

The usual causes: (1) self-invocation — a method calls another @Transactional method on this, bypassing the Spring proxy, so no transaction is started; (2) the method is private or otherwise not proxied; (3) a checked exception was thrown — by default only unchecked exceptions and errors trigger rollback, unless rollbackFor is set; (4) the exception was caught and swallowed inside the method; (5) work ran on another thread, which does not share the transaction. I would confirm with transaction debug logging before changing code.

What they are really testing: Understanding that declarative transactions are proxies, which explains nearly every symptom.

SeniorWhat is the N+1 query problem with JPA, and how do you fix it?

Loading 100 orders, then touching order.getCustomer() on each, fires 1 query for the orders plus 100 lazy-load queries. It hides in development with ten rows and melts in production. Detect it by logging SQL or counting statements in a test. Fix with a JOIN FETCH query, an @EntityGraph, batch fetching (hibernate.default_batch_fetch_size), or by querying straight into a DTO projection when you only need a few columns. Switching associations to eager fetching globally is not a fix — it moves the problem.

What they are really testing: Real ORM experience. People who have used Hibernate in production have all met this.

SeniorA Java service suddenly has high latency and 100% CPU. Walk me through the diagnosis.

First, is it the service at all — dashboards for request rate, downstream latency, GC. Then on the box: top -H to find the hot threads, and several thread dumps a few seconds apart (jcmd <pid> Thread.print) to see what they are doing consistently. Check GC logs: a heap near full causes back-to-back collections that burn CPU while the app does nothing useful. For a precise picture, a short Java Flight Recorder recording (jcmd <pid> JFR.start duration=60s) or async-profiler flame graph. Mitigate first (scale out, roll back), then fix the root cause.

What they are really testing: A method, in order, with real tools — and restoring service before root-causing.

SeniorThe service crashed with OutOfMemoryError. What do you do?

Read which kind: Java heap space, Metaspace, unable to create native thread and Direct buffer memory have different causes. For heap, production JVMs should already run with -XX:+HeapDumpOnOutOfMemoryError; open the dump, look at the dominator tree and the path to GC roots of the biggest retainers. Decide whether it is a leak (retained size grows forever — unbounded cache, listener list) or undersizing (legitimate peak load). In containers, check the heap is sized with -XX:MaxRAMPercentage relative to the container limit, not the host.

What they are really testing: That OOM has several flavours and that raising -Xmx without evidence is not diagnosis.

SeniorHow do you choose a garbage collector?

Start with the default, G1, and a correctly sized heap; most services never need more. Choose ZGC (generational) when tail latency matters and pauses of tens of milliseconds are unacceptable — large heaps, trading systems, interactive APIs — at some cost in throughput and memory. Choose Parallel for batch jobs where total throughput matters and pauses do not. Serial is for tiny heaps and single-CPU containers. Decide from GC logs and latency percentiles under realistic load, not from blog posts.

What they are really testing: Judgement tied to goals (latency vs throughput) and to measurement.

SeniorA client retries a payment request after a timeout. How do you make the API safe?

Make the operation idempotent: the client sends an Idempotency-Key header; the server stores the key with the result in the same transaction as the side effect, behind a unique constraint. A retry with the same key returns the stored result instead of charging twice; a concurrent duplicate hits the constraint. Pair it with timeouts, retries with backoff and jitter on the client, and the outbox pattern if the payment must also publish an event, so the database write and the message cannot disagree.

What they are really testing: Distributed-systems thinking: the network lies, so correctness has to live in the data model.

SeniorVirtual threads or a reactive stack like WebFlux — how would you decide for a new service?

For a typical I/O-bound service on Java 21+, virtual threads give most of the scalability of reactive code while keeping plain blocking code, readable stack traces and ordinary debugging — a big win for a team. Reactive still earns its place for true streaming (backpressure over long-lived connections, server-sent events at scale) or where the codebase and team are already reactive. Before choosing either, I would check the real bottleneck: if it is the database connection pool, neither model helps until that is addressed.

What they are really testing: Weighing team cost and debuggability against throughput, and finding the actual bottleneck.

SeniorHow would you split a large Spring monolith into services?

Usually slowly, and only where it pays. First make the monolith modular: packages by business capability with enforced boundaries (ArchUnit or Spring Modulith) and no shared tables across modules. Extract a service only where there is a clear reason — a different scaling profile, a team boundary, a release cadence — using the strangler pattern: route one capability to the new service behind the same API, move its data, then delete the old code. Plan for what distribution costs: network failures, eventual consistency, tracing, and on-call.

What they are really testing: Whether they treat microservices as a trade-off with costs, not a goal.

The senior-answer shape
Clarify the goal and constraints, name two options with their costs, pick one and say what would make you change your mind, then say how you would verify it in production. That structure matters more than any single fact.
10

The coding round, walked through

A live coding round is 30–45 minutes on one or two problems in a shared editor, often without an IDE’s autocomplete. The interviewer grades how you think, communicate and test, not only whether it compiles. Follow the same script every time — the one from Module 14.

  1. 1
    Clarify (2 min)

    Restate the problem. Ask about input size, empty input, duplicates, null, and what to return when there is no answer. Write the contract as a comment.

  2. 2
    Example (1 min)

    Work one small case by hand. It becomes your first test in main.

  3. 3
    Brute force out loud (2 min)

    "Count everything, sort it all, take the first k — O(n log n)." Say it and its cost before improving it.

  4. 4
    Pick the pattern (1 min)

    HashMap for counting? Two pointers? A PriorityQueue? Name it, and why.

  5. 5
    Code (15 min)

    Talk while you type. Declare with interfaces (Map, List), use real names, handle the edge cases you listed.

  6. 6
    Test (5 min)

    Run your example, then the edges. Finding your own bug scores higher than never having one.

  7. 7
    Complexity and improvements (2 min)

    State time and space, then the better version if there is one.

Here is a typical 30-minute problem solved that way: given a list of words, return the k most frequent; break ties alphabetically. The first version is the brute force with a Comparator; the second is the answer to "what would you improve?" — a size-k heap.

javaMain.java
import java.util.*;

public class Main {
    // Contract: words may be empty or repeat; k >= 0.
    // Returns the k most frequent words, ties broken alphabetically.
    static List<String> topK(List<String> words, int k) {
        Map<String, Integer> counts = new HashMap<>();
        for (String w : words) counts.merge(w, 1, Integer::sum);
        return counts.keySet().stream()
                .sorted(Comparator.comparing((String w) -> counts.get(w)).reversed()
                        .thenComparing(Comparator.naturalOrder()))
                .limit(k)
                .toList();                                   // O(n log n)
    }

    // Improvement for huge n, small k: keep only k candidates. O(n log k).
    static List<String> topKHeap(List<String> words, int k) {
        Map<String, Integer> counts = new HashMap<>();
        for (String w : words) counts.merge(w, 1, Integer::sum);
        PriorityQueue<String> heap = new PriorityQueue<>(
                Comparator.comparing((String w) -> counts.get(w))
                        .thenComparing(Comparator.reverseOrder())); // weakest on top
        for (String w : counts.keySet()) {
            heap.offer(w);
            if (heap.size() > k) heap.poll();
        }
        List<String> out = new ArrayList<>();
        while (!heap.isEmpty()) out.add(heap.poll());
        Collections.reverse(out);
        return out;
    }

    public static void main(String[] args) {
        System.out.println(topK(List.of("b", "a", "b", "c", "a", "b"), 2));
        System.out.println(topK(List.of(), 3));
        System.out.println(topK(List.of("x"), 0));
        System.out.println(topK(List.of("z", "y", "z", "y"), 1)); // tie -> alphabetical
        System.out.println(topKHeap(List.of("b", "a", "b", "c", "a", "b"), 2));
    }
}
Outputcompiled & run with real Java
[b, a]
[]
[]
[y]
[b, a]

The heap’s comparator is the reverse of the answer’s order, so the weakest candidate sits at the head and poll() evicts it.

Your turn

Add a test where every word appears once, e.g. List.of("d", "c", "b") with k = 2. Predict the output before running it — both methods must print [b, c].

What loses the round

  • Silence for ten minutes, then a wall of code
  • Fighting the syntax of generics instead of writing var or simplifying
  • Comparing strings with ==, or integers with a - b in a comparator
  • "It should work" without running an example
  • Optimising before the brute force is correct

What wins it

  • Narrating your reasoning, including dead ends
  • A written contract and example before code
  • Testing edge cases — empty list, k = 0, ties
  • Naming the complexity without being asked
  • "I use a PriorityQueue here because I only need k items"
11

Take-home assignment checklist

Java take-homes are usually "build a small REST API" or "process this file". Reviewers open the README, run the build, read the tests, then the code. Most rejected submissions fail at step two: it does not build on the reviewer’s machine.

  • Build wrapper committed: ./mvnw verify or ./gradlew build works on a clean machine with only a JDK installed. The Java version is pinned in pom.xml / build.gradle, not assumed.
  • README: what it does, how to build, run and test it in three commands, example requests (curl lines), and the decisions you made — including what you deliberately left out.
  • Tests with JUnit 5: the happy path, empty and invalid input, and the one tricky rule in the spec. For database code, Testcontainers against a real Postgres beats mocks of a repository.
  • Structure: packages by feature, a thin controller, logic in plain classes that can be tested without starting Spring.
  • Modern Java: records for DTOs, Optional for "may not exist", streams where they read better than loops — not everything in one style.
  • Errors on purpose: invalid input gets a 400 with a clear body, not a 500 and a stack trace. Exceptions keep their cause.
  • No noise in the repo: a .gitignore for target/, build/, .idea/ and *.iml; no secrets in application.properties.
  • Optional but noticed: a Dockerfile or docker-compose.yml so the reviewer can run it with the database in one command.
  • Commits: a handful of meaningful commits, not one "final" dump. Reviewers read the history.
  • Time-box to what they asked (usually 3–4 hours) and say so in the README. Five half-done extras are a red flag; one finished extra is fine.
The sentence reviewers want to write
"Built first time, tests cover the edge cases, the code reads like the team already wrote it." Aim every decision at that sentence. The next module, Job Ready, turns the same standards into a portfolio.

Frequently asked questions

What Java topics are asked most in interviews?
At junior level: == versus equals, String immutability, OOP pillars, overloading versus overriding, checked versus unchecked exceptions and choosing collections. At mid level: HashMap internals, the equals/hashCode contract, generics and streams, synchronized versus volatile, and garbage collection. Senior rounds add Spring transactions, JPA performance and production debugging.
Do Java interviews expect Spring Boot knowledge?
For backend roles, usually yes — most Java services are built on Spring Boot. Junior screens focus on core Java, but mid and senior rounds commonly ask about dependency injection, @Transactional behaviour and JPA pitfalls such as the N+1 query problem.
Which Java version should I prepare for?
Prepare on Java 21, the current long-term-support release most new projects target, and know which features arrived when — records (16), sealed classes (17), pattern-matching switch and virtual threads (21) — because many teams still run Java 17 or older.

Finish the Java handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.