Ace Your Interviews 🎯
Browse our collection of interview questions across various technologies.
What is Java and what is the JVM?
Java is a statically typed, object-oriented, platform-independent programming language. The JVM (Java Virtual Machine) is the runtime environment that executes compiled Java bytecode. The JVM makes Java platform-independent — the same .class file runs on Windows, Linux, and macOS without recompilation. The JDK (Java Development Kit) includes the compiler (javac) and the JVM.
What is the difference between JDK, JRE, and JVM?
JVM: executes bytecode — the runtime engine. JRE (Java Runtime Environment): JVM + standard class libraries — needed to run Java programs. JDK (Java Development Kit): JRE + compiler (javac) + development tools — needed to develop Java programs. Developers need JDK; end users only need JRE.
What are the four pillars of OOP in Java?
Encapsulation: hiding internal data through private fields and public methods. Inheritance: a subclass extends a superclass, inheriting and potentially overriding its behavior. Polymorphism: a reference of a parent type can hold child objects; method calls resolve to the actual runtime type. Abstraction: hiding implementation details behind interfaces and abstract classes.
What is the difference between == and .equals() in Java?
== compares object references (memory addresses) for objects, and values for primitives. .equals() compares object content. For Strings: 'hello' == 'hello' may be true for string literals (same pool object) but is unreliable. Always use .equals() for String and object comparison. Always use == for primitives (int, double, boolean).
What is the difference between an interface and an abstract class?
Abstract class: can have state (fields), constructors, and method implementations (abstract and concrete). Supports single inheritance. Use when subclasses share common implementation. Interface: no state (only constants), no constructors, all methods abstract by default (Java 8+ allows default and static methods). Supports multiple implementation. Use to define a contract. Choose interface when defining capability; abstract class when sharing code.
What are Java's primitive data types?
8 primitives: byte (8-bit integer), short (16-bit integer), int (32-bit integer), long (64-bit integer), float (32-bit floating point), double (64-bit floating point), char (16-bit Unicode character), boolean (true/false). Everything else is an object. Wrapper classes (Integer, Double, Boolean) box primitives into objects — needed for collections.
What is the difference between ArrayList and LinkedList?
ArrayList: backed by a dynamic array. O
random access (get/set by index), O(n) insert/delete in the middle, O
amortized add to end. LinkedList: doubly-linked nodes. O(n) random access, O
insert/delete at head and tail. Use ArrayList for most cases (better cache performance). Use LinkedList when you frequently insert/delete at both ends (as a Deque/Queue).
What is a checked exception vs an unchecked exception?
Checked exceptions: must be handled (try-catch) or declared (throws) — IOException, SQLException, ParseException. The compiler enforces this. Unchecked exceptions (RuntimeException subclasses): optional to handle — NullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException. They indicate programming errors. Error (OutOfMemoryError, StackOverflowError) — JVM-level, should not be caught.
What is the difference between String, StringBuilder, and StringBuffer?
String: immutable — every modification creates a new object. Safe to share, expensive to concatenate in loops. StringBuilder: mutable, not thread-safe, fast — use for single-threaded string building. StringBuffer: mutable, thread-safe (synchronized methods), slower — rarely needed; StringBuilder is almost always preferable. For string concatenation in loops: always use StringBuilder.
What is autoboxing and unboxing in Java?
Autoboxing: automatic conversion from primitive to wrapper (int → Integer). Unboxing: wrapper to primitive (Integer → int). Java performs this automatically when primitives are added to collections or assigned to wrapper types. Caveat: unboxing a null Integer throws NullPointerException — always check for null before unboxing.
How does HashMap work internally in Java?
HashMap uses an array of 'buckets'. The key's hashCode() determines the bucket index (index = hash % capacity). Multiple keys in the same bucket are stored as a linked list (Java 8+ converts to a balanced tree when bucket size > 8 for O(log n) worst-case). Get/put is O
average when hashCode distributes evenly. Requires both hashCode() and equals() to be correctly overridden for keys.
What is the Java Memory Model? Explain heap vs stack.
Stack: per-thread, stores method frames, local variables, and references — LIFO, fast, automatically managed, finite size. Heap: shared across all threads, stores all objects — managed by Garbage Collector, much larger. Primitive local variables go on the stack. Objects always go on the heap; the stack holds a reference to them. Static fields go in the Metaspace (Java 8+, previously PermGen).
Explain the Java Garbage Collector. What is GC tuning?
GC automatically reclaims heap memory from objects no longer reachable from any GC root (thread stacks, static fields). Modern JVMs use generational collection: Young Generation (new objects, frequent minor GC), Old Generation (long-lived objects, infrequent major/full GC). G1GC is the default in Java 9+; ZGC and Shenandoah offer low-latency GC in Java 11+. Tuning involves setting heap size (-Xmx, -Xms), choosing GC algorithm, and minimizing object allocation.
What is the difference between volatile and synchronized?
volatile: guarantees visibility — all threads see the latest value written by any thread. Does NOT prevent race conditions with compound operations (read-modify-write). synchronized: provides both visibility AND atomicity — only one thread executes the synchronized block at a time. Use volatile for simple flags (boolean, reference). Use synchronized (or atomic classes) when read-modify-write must be atomic.
What is a deadlock and how do you prevent it?
Deadlock: two or more threads each hold a lock the other needs — both wait forever. Conditions: mutual exclusion, hold and wait, no preemption, circular wait. Prevention: always acquire locks in the same order across all threads (eliminates circular wait). Use tryLock() with timeout (ReentrantLock). Design stateless services (no shared mutable state → no locks needed).
What is the Stream API and what is the difference between intermediate and terminal operations?
Stream API processes collections declaratively. Intermediate operations (filter, map, sorted, distinct) are lazy — they build a pipeline but don't execute until a terminal operation is called. Terminal operations (collect, forEach, reduce, count, findFirst) trigger execution of the pipeline. Streams are not reusable — once a terminal operation is called, the stream is consumed.
What are generics and what is type erasure?
Generics provide compile-time type safety for collections and classes — List<String> ensures only Strings can be added. Type erasure: at compile time, generic type information is removed — List<String> becomes List at runtime. This means you can't do instanceof List<String> at runtime, and reflection doesn't see generic types. Type erasure maintains backward compatibility with pre-Java-5 code.
What is the difference between Comparable and Comparator?
Comparable: implemented by the class itself (implements Comparable<T>), defines the 'natural order' via compareTo(). Used when the class has one obvious sort order. Comparator: external comparator object, can be created as a lambda, defines custom sort orders. Used when you need multiple sort orders or can't modify the class. Collections.sort() and List.sort() accept both.
What is the Singleton design pattern and how do you make it thread-safe?
Singleton ensures one instance of a class. Thread-safe implementations:
Enum singleton — serialization-safe, thread-safe by JVM guarantee, simplest.
Bill Pugh Singleton — static inner class, lazy initialization, thread-safe without synchronization.
Double-checked locking with volatile — lazy, thread-safe but complex. In Spring, every @Service/@Repository is a Singleton managed by Spring's container — no custom Singleton pattern needed.
What is the difference between fail-fast and fail-safe iterators?
Fail-fast iterators (ArrayList, HashMap's iterators): throw ConcurrentModificationException if the collection is modified during iteration — by tracking a modCount. Fail-safe iterators (ConcurrentHashMap, CopyOnWriteArrayList): work on a snapshot or use concurrent structures, don't throw exceptions but may not reflect latest modifications. For thread-safe iteration, use fail-safe concurrent collections.
Explain Java's memory model (JMM) and the happens-before relationship.
JMM defines how threads interact through memory — when writes by one thread become visible to another. Happens-before relationship: if action A happens-before B, A's memory effects are visible to B. Established by: synchronized blocks, volatile reads/writes, thread.start(), thread.join(), and Lock.unlock() → Lock.lock(). Without happens-before, compiler and CPU can reorder instructions, causing threads to see stale values.
What are the differences between ReentrantLock and synchronized?
ReentrantLock advantages: try to acquire with timeout (tryLock(timeout)), interruptible lock acquisition, fairness option (longest-waiting thread gets lock first), multiple Condition objects per lock. synchronized advantages: simpler syntax, JVM can optimize it aggressively. Use synchronized for simple mutual exclusion; ReentrantLock when you need advanced features (tryLock, fairness, multiple conditions).
How does CompletableFuture work and when would you use it?
CompletableFuture is a Future that can be explicitly completed, supports non-blocking callbacks (thenApply, thenCompose, thenCombine), and can compose multiple async computations. Use for: running tasks in parallel and combining results (thenCombine, allOf), chaining dependent async operations (thenCompose), handling failures gracefully (exceptionally, handle). Replaces the older Future.get() pattern which blocks.
What is ClassLoader and how does the class loading process work?
ClassLoader loads .class files into the JVM. Three built-in loaders: Bootstrap ClassLoader (core Java classes — rt.jar), Extension ClassLoader (ext directory), Application ClassLoader (classpath). Delegation model: a class loader first delegates to its parent before trying to load itself — ensures core Java classes can't be overridden by user classes. Custom class loaders enable hot-reloading (application servers load new class files without restart).
Explain the difference between G1GC, ZGC, and Shenandoah.
G1GC (Java 9+ default): divides heap into equal regions, collects regions with most garbage first (Garbage First). Aims for predictable pause times. Good balance of throughput and latency. ZGC (Java 15+ production-ready): ultra-low latency GC, sub-millisecond pauses regardless of heap size (terabyte heaps), concurrent marking and compaction. Shenandoah: similar to ZGC, concurrent compaction, designed for low-pause-time applications. Use ZGC/Shenandoah for latency-sensitive services (trading systems, real-time APIs).
What is the difference between JIT compilation and AOT compilation in Java?
JIT (Just-In-Time): the JVM compiles frequently-executed bytecode to native machine code at runtime ('hot paths'). JVM warms up over minutes. Optimal for long-running server applications — performance improves over time. AOT (Ahead-Of-Time, GraalVM native image): compiles to native binary at build time. Starts instantly (milliseconds vs seconds), uses less memory, but no JIT optimization at runtime. Best for serverless functions and CLI tools where startup time matters.
How does Java serialization work and what are its security concerns?
Java serialization (implements Serializable) converts objects to byte streams for storage or network transmission. ObjectOutputStream.writeObject() → ObjectInputStream.readObject(). Security concerns: deserialization of untrusted data can execute arbitrary code (gadget chains — Apache Commons Collections vulnerability). Mitigations: use Java 9+'s serialization filter (ObjectInputFilter), use safer alternatives (JSON with Jackson, Protocol Buffers, Kryo with allowlist). Only serialize internal objects you fully control.
What is Project Loom and how do virtual threads change Java concurrency?
Project Loom (Java 21+ GA): introduces virtual threads — lightweight threads managed by the JVM (not OS threads). You can create millions of virtual threads without running out of OS thread resources. Virtual threads use the same Thread API — existing code works without changes. They are perfect for I/O-bound applications (web servers, database clients). One virtual thread per request model becomes viable at massive concurrency — eliminating the need for reactive programming (WebFlux) in many cases.
How would you find and fix a memory leak in a Java application?
Identify: heap memory grows over time, eventually OutOfMemoryError. Tools: Java Flight Recorder (JFR), JProfiler, VisualVM — take heap snapshots over time, compare retained sizes. Common causes: static collections holding objects (static Map growing without eviction), unclosed resources in try blocks, event listeners not removed, ThreadLocal variables not removed in thread pool threads. Fix: clear references when done, use WeakHashMap for caches, always close resources, remove listeners on cleanup, call ThreadLocal.remove() in finally.
Explain the Java 9 Module System (JPMS).
JPMS (Java Platform Module System) organizes code into modules — each declares what packages it exports and what other modules it requires. Provides strong encapsulation (packages not exported are inaccessible even via reflection), reliable configuration (missing dependencies fail at startup not runtime), improved security. Defined in module-info.java. Most relevant in large codebases and frameworks — most applications can use unnamed module (classpath) without adopting JPMS explicitly.
What is the difference between ExecutorService.submit() and execute()?
execute(Runnable): fire-and-forget — no return value, exceptions are silently discarded (unless UncaughtExceptionHandler is set). submit(Callable): returns a Future<T> — you can get the result, and exceptions are captured and re-thrown when you call future.get(). Always use submit() when you need results or must handle task exceptions. execute() is for true fire-and-forget background tasks where you don't care about completion or exceptions.