Core Java¶
OOP and language basics¶
What are the four pillars of OOP?
Encapsulation (hide state behind methods), abstraction (expose what, hide how: interfaces, abstract classes), inheritance (reuse and specialise a type), polymorphism (one interface, many implementations; method overriding at runtime, overloading at compile time). Prefer composition over inheritance for reuse.
Abstract class vs. interface?
An interface defines a contract; since Java 8 it can have default and static methods, since 9 private
methods, but no instance state. A class can implement many interfaces. An abstract class can hold state and
constructors, and a class extends only one. Use an interface for a capability ("Comparable"), an abstract class for
a shared base with state. Sealed interfaces (17) restrict who can implement.
== vs. equals(); why override hashCode with equals?
== compares references (or primitive values); equals compares logical equality. The contract: equal objects
must have equal hash codes. If you override equals but not hashCode, two equal keys land in different
HashMap buckets and get fails. Use the same fields in both; records generate both for you.
Why is String immutable? What is the string pool?
Immutability makes strings safe to share across threads, safe as HashMap keys (hash cached), and safe for
security-sensitive values (class names, URLs). The pool interns string literals so identical literals share one
object. new String("a") creates a new object; intern() returns the pooled one. Use StringBuilder for
concatenation in loops (StringBuffer is the synchronised, slower version).
How do you make a class immutable?
final class (or private constructor + factory), all fields private final, no setters, defensive copies of
mutable inputs and outputs (List.copyOf), and don't leak this in the constructor. Or use a record (shallowly
immutable: still copy mutable components).
Checked vs. unchecked exceptions?
Checked (Exception but not RuntimeException) must be declared or caught: for recoverable conditions the caller
should handle (IOException). Unchecked (RuntimeException) for programming errors (NullPointerException,
IllegalArgumentException). Error (OutOfMemoryError) is not meant to be caught. Modern style favours
unchecked plus clear documentation; Spring translates checked SQL exceptions into unchecked
DataAccessException. Always use try-with-resources for AutoCloseable.
final, finally, finalize?
final: a variable can't be reassigned, a method can't be overridden, a class can't be extended. finally: block
that runs after try/catch (except System.exit or JVM crash). finalize: deprecated (Java 9, for removal in 18)
cleanup hook; use try-with-resources or Cleaner.
Is Java pass-by-value or pass-by-reference?
Always pass-by-value. For objects, the value passed is the reference, so a method can mutate the object but can't make the caller's variable point to a different object.
What is static? Can you override a static method?
Belongs to the class, not an instance. Static methods are hidden, not overridden: the call is resolved by the declared type at compile time, so no polymorphism.
Explain generics and type erasure. What is ? extends vs. ? super?
Generics give compile-time type safety; at runtime type parameters are erased to their bounds (so no
new T(), no instanceof List<String>). PECS: Producer extends, Consumer super. A
List<? extends Number> can be read as Number but you can't add (except null); a List<? super Integer> accepts
Integer but reads as Object. Example: Collections.copy(List<? super T> dest, List<? extends T> src).
Collections¶
How does HashMap work internally?
An array of buckets. put computes hashCode(), spreads it (h ^ (h >>> 16)), picks bucket hash & (n-1) (n is
a power of two). Collisions go in a linked list in the bucket; when a bucket has more than 8 entries (and the table
is at least 64), it becomes a red-black tree (Java 8), so worst case is O(log n) instead of O(n). When size
exceeds capacity × load factor (default 16 × 0.75), the table doubles and entries are redistributed.
Allows one null key. Not thread-safe.
Prayog link: HashMap<Long, RestingOrder> allocated a Node per entry plus a boxed Long; I replaced it with
an open-addressing map over primitive arrays and cut allocation 12–21%.
HashMap vs. Hashtable vs. ConcurrentHashMap vs. Collections.synchronizedMap?
Hashtable and synchronizedMap: one lock for every operation (slow under contention). ConcurrentHashMap:
lock-free reads, CAS for empty buckets and a lock per bucket for writes (Java 8+); no null keys or values; atomic
compound ops (computeIfAbsent, merge). Iterators are weakly consistent (never throw
ConcurrentModificationException). Use CHM for shared maps.
ArrayList vs. LinkedList?
ArrayList: backed by an array, O(1) random access, amortised O(1) append, O(n) insert in the middle,
cache-friendly. LinkedList: O(1) insert/remove at a known node, O(n) access, a node object per element (memory,
cache misses). In practice ArrayList (or ArrayDeque for queues) wins almost always.
HashMap vs. LinkedHashMap vs. TreeMap?
HashMap: no order, O(1). LinkedHashMap: insertion (or access) order; access order + removeEldestEntry makes
an LRU cache. TreeMap: sorted by key (red-black tree), O(log n), with firstKey, floorKey, headMap.
Prayog link: the order book is a TreeMap of price levels per side, so the best price is firstKey() in
O(log n).
What is fail-fast vs. fail-safe iteration?
Fail-fast iterators (ArrayList, HashMap) throw ConcurrentModificationException if the collection is
structurally modified during iteration (other than through the iterator's own remove); detected via a modCount.
Fail-safe / weakly consistent iterators (CopyOnWriteArrayList, ConcurrentHashMap) work on a snapshot or
tolerate changes.
Comparable vs. Comparator?
Comparable.compareTo defines the natural order inside the class. Comparator is an external strategy;
compose with Comparator.comparing(Person::age).thenComparing(Person::name).reversed().
Java 8+ features¶
What did Java 8 introduce?
Lambdas and functional interfaces, streams, Optional, default methods, method references, the java.time API,
CompletableFuture, ConcurrentHashMap improvements, Nashorn (since removed).
Explain streams: intermediate vs. terminal, laziness, parallel streams.
A pipeline: source → intermediate ops (map, filter, sorted: lazy, return a stream) → terminal op (collect,
reduce, forEach: triggers execution). Lazy means elements flow one at a time and short-circuit ops (findFirst,
limit) stop early. A stream can be consumed once. Parallel streams use the common ForkJoinPool; useful only for
large CPU-bound work with no shared state; avoid for I/O or in a server where the common pool is shared.
map vs. flatMap?
map transforms each element one-to-one. flatMap maps each element to a stream and flattens:
orders.stream().flatMap(o -> o.lines().stream()).
Write: group employees by department and count / average salary.
Map<String, Long> countByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));
Map<String, Double> avgSalary = employees.stream()
.collect(Collectors.groupingBy(Employee::dept,
Collectors.averagingDouble(Employee::salary)));
// Highest paid per department
Map<String, Optional<Employee>> top = employees.stream()
.collect(Collectors.groupingBy(Employee::dept,
Collectors.maxBy(Comparator.comparingDouble(Employee::salary))));
// Second highest salary overall
Optional<Double> second = employees.stream().map(Employee::salary).distinct()
.sorted(Comparator.reverseOrder()).skip(1).findFirst();
How should you use Optional?
As a return type for "maybe no result". Use map, orElse, orElseGet (lazy), orElseThrow. Don't use it for
fields, parameters or collections (return an empty list), and don't call get() without checking.
What is a functional interface? Name the main ones.
An interface with exactly one abstract method, usable as a lambda target. Function<T,R>, Supplier<T>,
Consumer<T>, Predicate<T>, BiFunction, UnaryOperator, Runnable, Callable.
What's new in Java 11, 17 and 21 that you actually use?
- 11:
varin lambdas,String.isBlank/strip/lines/repeat,HttpClient,Files.readString. - 17 (LTS): records, sealed classes, pattern matching for
instanceof, switch expressions, text blocks. - 21 (LTS): virtual threads, record patterns, pattern matching for
switch, sequenced collections (getFirst,reversed), generational ZGC.
Prayog link: Java 21 throughout: one sealed Command type with records for each command, and the engine
switches over them with pattern matching.
What are records? Limitations?
record Point(int x, int y) {} gives a final class with private final fields, a canonical constructor, accessors,
equals, hashCode and toString. Can add compact constructors for validation and methods. Can't extend
another class, fields are final (shallow immutability), no extra instance fields.
What are sealed classes and why pair them with records?
sealed interface Command permits NewOrder, Cancel, Modify restricts the subtypes. With records and pattern
matching, switch (cmd) { case NewOrder n -> ...; case Cancel c -> ...; } is exhaustive: the compiler errors
if you add a command and forget to handle it. Algebraic data types in Java.
What are virtual threads? When would you not use them?
Lightweight threads managed by the JVM (Project Loom, Java 21), mounted on a small pool of carrier OS threads. A
blocking call unmounts the virtual thread, so you can have millions and write simple blocking code for I/O-heavy
servers instead of reactive code. Not useful for CPU-bound work. Watch for pinning (a virtual thread blocking
inside synchronized or native code holds its carrier; improved in Java 24) and for ThreadLocal-heavy code.
Don't pool them. Spring Boot: spring.threads.virtual.enabled=true.
Design principles and patterns¶
Explain SOLID with an example each.
- Single responsibility: a class has one reason to change (
ReportGeneratordoesn't also send email). - Open/closed: add behaviour by adding code, not editing it (a new
ProductTypestrategy, not anotherif). - Liskov substitution: subtypes must work wherever the base type does (
Square extends Rectanglebreaks it). - Interface segregation: small focused interfaces, not one fat one.
- Dependency inversion: depend on abstractions; inject them (Spring DI).
Which design patterns have you used?
- Strategy: type-specific behaviour (ALT vs. SMA validation).
- Builder: complex immutable objects (Lombok
@Builder, PDF document config). - Factory: create the right implementation from a type.
- Observer / pub-sub: Kafka events, React state subscriptions.
- Template method: report generation with fixed steps and varying parts.
- Adapter: mapping another team's API (Heatmap) to our model.
- Singleton: Spring beans are singletons by default (container-managed, not the static pattern).
- Decorator / Proxy: Spring AOP (
@Transactional,@Cacheable).
Write a thread-safe singleton.
// Simplest and safest: enum (serialisation- and reflection-safe)
public enum Config { INSTANCE; }
// Lazy, via the holder idiom (class init is thread-safe)
public final class Registry {
private Registry() {}
private static class Holder { static final Registry INSTANCE = new Registry(); }
public static Registry get() { return Holder.INSTANCE; }
}
// Double-checked locking needs volatile
private static volatile Service instance;
public static Service get() {
if (instance == null) {
synchronized (Service.class) {
if (instance == null) instance = new Service();
}
}
return instance;
}
volatile is needed because without it another thread can see a non-null reference to a partly constructed
object (reordering).