Top Java Interview Questions & Answers
A comprehensive collection of the most frequently asked Java interview questions with practical code examples and detailed explanations.
๐ Table of Contentsโ
- Java Basics
- OOP Concepts
- String & Memory
- Collections Framework
- Exception Handling
- Multithreading
- Java 8+ Features
- JVM & Memory Management
- Design Patterns
- Coding Problems
Java Basicsโ
1. What is Java and its key features?โ
Answer: Java is a platform-independent, object-oriented programming language.
Key Features:
- Platform Independent: Write once, run anywhere (WORA)
- Object-Oriented: Encapsulation, Inheritance, Polymorphism, Abstraction
- Secure: No pointers, bytecode verification, security manager
- Multithreaded: Built-in support for concurrent programming
- Automatic Memory Management: Garbage collection
// Platform independence through JVM
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
// Same bytecode runs on Windows, Linux, Mac
2. JDK vs JRE vs JVMโ
Answer:
| Component | Description | Contains |
|---|---|---|
| JVM | Java Virtual Machine | Executes bytecode |
| JRE | Java Runtime Environment | JVM + Libraries |
| JDK | Java Development Kit | JRE + Development Tools |
// JDK provides compiler (javac)
javac HelloWorld.java // Compiles to bytecode
// JRE provides runtime
java HelloWorld // Executes bytecode
// JVM executes the bytecode
3. Primitive vs Reference typesโ
Answer:
- Primitive Types
- Reference Types
// Stored in stack, passed by value
int a = 10;
double b = 3.14;
boolean c = true;
char d = 'A';
// 8 primitive types:
byte, short, int, long, float, double, char, boolean
// Stored in heap, passed by reference
String str = "Hello";
Integer num = 42;
int[] array = {1, 2, 3};
List<String> list = new ArrayList<>();
// All classes, interfaces, arrays are reference types
4. == vs equals() vs hashCode()โ
Answer:
public class EqualsExample {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
// == compares references
System.out.println(s1 == s2); // true (string pool)
System.out.println(s1 == s3); // false (different objects)
// equals() compares content
System.out.println(s1.equals(s3)); // true
// hashCode() for hash-based collections
System.out.println(s1.hashCode() == s3.hashCode()); // true
}
}
// Custom class example
class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
5. Access modifiers in Javaโ
Answer:
| Modifier | Same Class | Same Package | Subclass | Different Package |
|---|---|---|---|---|
| private | โ | โ | โ | โ |
| default | โ | โ | โ | โ |
| protected | โ | โ | โ | โ |
| public | โ | โ | โ | โ |
public class AccessExample {
private int privateVar = 1; // Only within class
int defaultVar = 2; // Within package
protected int protectedVar = 3; // Within package + subclasses
public int publicVar = 4; // Everywhere
private void privateMethod() { }
protected void protectedMethod() { }
public void publicMethod() { }
}
OOP Conceptsโ
6. Encapsulation with exampleโ
Answer: Bundling data and methods together, hiding internal implementation.
public class BankAccount {
private double balance; // Private data
private String accountNumber;
// Public methods to access private data
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
public double getBalance() {
return balance; // Controlled access
}
// No direct access to balance field
}
7. Inheritance types and exampleโ
Answer:
- Single Inheritance
- Multilevel Inheritance
- Multiple Inheritance (Interfaces)
// Base class
class Animal {
protected String name;
public void eat() {
System.out.println(name + " is eating");
}
}
// Derived class
class Dog extends Animal {
public void bark() {
System.out.println(name + " is barking");
}
}
class Animal {
public void breathe() { }
}
class Mammal extends Animal {
public void giveBirth() { }
}
class Dog extends Mammal {
public void bark() { }
}
// Dog inherits from Mammal, which inherits from Animal
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("Duck is flying");
}
@Override
public void swim() {
System.out.println("Duck is swimming");
}
}
8. Polymorphism - Method Overloading vs Overridingโ
Answer:
- Method Overloading (Compile-time)
- Method Overriding (Runtime)
public class Calculator {
// Same method name, different parameters
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
// Compiler decides which method to call
}
class Shape {
public void draw() {
System.out.println("Drawing a shape");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// Runtime polymorphism
Shape shape = new Circle();
shape.draw(); // Calls Circle's draw() method
9. Abstract class vs Interfaceโ
Answer:
- Abstract Class
- Interface
abstract class Vehicle {
protected String brand; // Can have instance variables
public Vehicle(String brand) { // Can have constructor
this.brand = brand;
}
public void start() { // Can have concrete methods
System.out.println("Vehicle starting...");
}
abstract void move(); // Must be implemented by subclass
}
class Car extends Vehicle {
public Car(String brand) {
super(brand);
}
@Override
void move() {
System.out.println("Car is moving on road");
}
}
interface Drawable {
// All variables are public, static, final
int MAX_SIZE = 100;
// All methods are public and abstract (before Java 8)
void draw();
void resize(int size);
// Default method (Java 8+)
default void print() {
System.out.println("Printing...");
}
// Static method (Java 8+)
static void info() {
System.out.println("Drawable interface");
}
}
class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing circle");
}
@Override
public void resize(int size) {
System.out.println("Resizing circle to " + size);
}
}
10. final, finally, finalizeโ
Answer:
// final keyword
final class FinalClass { } // Cannot be extended
class Parent {
final void finalMethod() { } // Cannot be overridden
}
class Child extends Parent {
final int CONSTANT = 10; // Cannot be reassigned
}
// finally block
public void exampleMethod() {
try {
// risky code
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Exception caught");
} finally {
// Always executes (except System.exit())
System.out.println("Cleanup code");
}
}
// finalize method (deprecated in Java 9+)
class Resource {
@Override
protected void finalize() throws Throwable {
// Called by GC before object destruction
// Not guaranteed to be called
super.finalize();
}
}
String & Memoryโ
11. String, StringBuilder, StringBufferโ
Answer:
- String (Immutable)
- StringBuilder (Mutable, Not Thread-safe)
- StringBuffer (Mutable, Thread-safe)
String str = "Hello";
str = str + " World"; // Creates new String object
// Original "Hello" becomes eligible for GC
// String pool optimization
String s1 = "Java"; // In string pool
String s2 = "Java"; // Points to same object
String s3 = new String("Java"); // New object in heap
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // Modifies existing buffer
sb.insert(5, ","); // "Hello, World"
sb.reverse(); // "dlroW ,olleH"
// Efficient for multiple string operations
StringBuilder result = new StringBuilder();
for (int i = 0; i < 1000; i++) {
result.append("Item ").append(i).append(" ");
}
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // Synchronized methods
// Thread-safe but slower than StringBuilder
// Use when multiple threads access same buffer
12. Memory areas in JVMโ
Answer:
public class MemoryExample {
static int staticVar = 100; // Method Area
public void method() {
int localVar = 10; // Stack
String str = "Hello"; // String Pool (Heap)
Object obj = new Object(); // Heap
// Stack: method parameters, local variables, method calls
// Heap: objects, instance variables, arrays
// Method Area: class metadata, static variables, constants
}
}
// Memory allocation example
class Person {
String name; // Instance variable (Heap)
static int count; // Static variable (Method Area)
public Person(String name) {
this.name = name; // 'this' reference (Stack)
count++; // Accessing static variable
}
}
13. Garbage Collection basicsโ
Answer:
public class GCExample {
public static void main(String[] args) {
// Object creation
Person p1 = new Person("John");
Person p2 = new Person("Jane");
// Making objects eligible for GC
p1 = null; // No reference to "John" object
p2 = p1; // "Jane" object becomes unreachable
// Suggest GC (not guaranteed)
System.gc();
// Objects with circular references
Node node1 = new Node();
Node node2 = new Node();
node1.next = node2;
node2.next = node1; // Circular reference
node1 = null;
node2 = null; // Both become eligible for GC
}
}
class Node {
Node next;
// GC can handle circular references
}
Collections Frameworkโ
14. List vs Set vs Mapโ
Answer:
- List (Ordered, Duplicates allowed)
- Set (No duplicates)
- Map (Key-Value pairs)
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Apple"); // Duplicates allowed
list.add(1, "Orange"); // Insert at index
// ArrayList vs LinkedList
List<String> arrayList = new ArrayList<>(); // Fast random access
List<String> linkedList = new LinkedList<>(); // Fast insertion/deletion
// Vector (synchronized ArrayList)
List<String> vector = new Vector<>(); // Thread-safe but slower
Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Apple"); // Ignored (duplicate)
Set<String> linkedHashSet = new LinkedHashSet<>(); // Maintains insertion order
Set<String> treeSet = new TreeSet<>(); // Sorted order
// Custom object in Set
Set<Person> personSet = new HashSet<>();
// Requires proper equals() and hashCode() implementation
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 5);
map.put("Banana", 3);
map.put("Apple", 7); // Overwrites previous value
// Different Map implementations
Map<String, Integer> hashMap = new HashMap<>(); // No order
Map<String, Integer> linkedHashMap = new LinkedHashMap<>(); // Insertion order
Map<String, Integer> treeMap = new TreeMap<>(); // Sorted by keys
// Iteration
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
15. HashMap internal workingโ
Answer:
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// Internal process:
// 1. Calculate hash of key: "Apple".hashCode()
// 2. Find bucket: hash % buckets.length
// 3. Handle collision: chaining or open addressing
// 4. Store key-value pair
map.put("Apple", 5); // hash("Apple") -> bucket index
map.put("Banana", 3); // hash("Banana") -> different bucket
// Collision handling
map.put("FB", 1); // Same hash as "Ea"
map.put("Ea", 2); // Collision -> chaining
}
}
// Custom key class
class CustomKey {
private String key;
@Override
public int hashCode() {
return key.hashCode(); // Important for HashMap
}
@Override
public boolean equals(Object obj) {
// Must be consistent with hashCode()
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
CustomKey that = (CustomKey) obj;
return Objects.equals(key, that.key);
}
}
16. Comparable vs Comparatorโ
Answer:
- Comparable (Natural ordering)
- Comparator (Custom ordering)
class Student implements Comparable<Student> {
private String name;
private int age;
@Override
public int compareTo(Student other) {
return this.age - other.age; // Sort by age
// return this.name.compareTo(other.name); // Sort by name
}
}
// Usage
List<Student> students = Arrays.asList(
new Student("Alice", 22),
new Student("Bob", 20),
new Student("Charlie", 25)
);
Collections.sort(students); // Uses compareTo()
class Student {
private String name;
private int age;
private double gpa;
// Getters...
}
// Multiple sorting strategies
Comparator<Student> byAge = (s1, s2) -> s1.getAge() - s2.getAge();
Comparator<Student> byName = (s1, s2) -> s1.getName().compareTo(s2.getName());
Comparator<Student> byGPA = Comparator.comparing(Student::getGpa);
// Usage
Collections.sort(students, byAge);
Collections.sort(students, byName.reversed());
Collections.sort(students, byGPA.thenComparing(Student::getName));
17. Fail-fast vs Fail-safe iteratorsโ
Answer:
// Fail-fast (ArrayList, HashMap)
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("B".equals(item)) {
list.remove(item); // ConcurrentModificationException
}
}
// Correct way with fail-fast
Iterator<String> safeIterator = list.iterator();
while (safeIterator.hasNext()) {
String item = safeIterator.next();
if ("B".equals(item)) {
safeIterator.remove(); // Safe removal
}
}
// Fail-safe (ConcurrentHashMap, CopyOnWriteArrayList)
List<String> safeList = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : safeList) {
if ("B".equals(item)) {
safeList.remove(item); // No exception, works on copy
}
}
Exception Handlingโ
18. Checked vs Unchecked exceptionsโ
Answer:
- Checked Exceptions
- Unchecked Exceptions
// Must be handled or declared
public void readFile(String filename) throws IOException {
FileReader file = new FileReader(filename); // IOException
// Compiler forces handling
}
// Handle with try-catch
public void handleCheckedException() {
try {
readFile("test.txt");
} catch (IOException e) {
System.out.println("File not found: " + e.getMessage());
}
}
// Common checked exceptions:
// IOException, SQLException, ClassNotFoundException
// Runtime exceptions - not required to handle
public void uncheckedExamples() {
// NullPointerException
String str = null;
// int length = str.length(); // Runtime exception
// ArrayIndexOutOfBoundsException
int[] arr = {1, 2, 3};
// int value = arr[5]; // Runtime exception
// ArithmeticException
// int result = 10 / 0; // Runtime exception
}
// Optional handling
public void optionalHandling() {
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Division by zero");
}
}
19. Custom exceptions and best practicesโ
Answer:
// Custom checked exception
class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(String message, double amount) {
super(message);
this.amount = amount;
}
public double getAmount() { return amount; }
}
// Custom unchecked exception
class InvalidAccountException extends RuntimeException {
public InvalidAccountException(String message) {
super(message);
}
}
// Usage
class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"Insufficient funds. Available: " + balance, amount);
}
balance -= amount;
}
}
// Exception handling best practices
public class ExceptionBestPractices {
public void goodPractices() {
try {
// Specific exceptions first
riskyOperation();
} catch (InsufficientFundsException e) {
// Handle specific exception
System.out.println("Insufficient funds: " + e.getAmount());
} catch (InvalidAccountException e) {
// Handle another specific exception
System.out.println("Invalid account: " + e.getMessage());
} catch (Exception e) {
// Generic handler last
System.out.println("Unexpected error: " + e.getMessage());
} finally {
// Cleanup code
closeResources();
}
}
// Try-with-resources (Java 7+)
public void tryWithResources() {
try (FileReader file = new FileReader("test.txt");
BufferedReader reader = new BufferedReader(file)) {
// Resources automatically closed
String line = reader.readLine();
} catch (IOException e) {
System.out.println("File error: " + e.getMessage());
}
}
}
Multithreadingโ
20. Creating threads in Javaโ
Answer:
- Extending Thread class
- Implementing Runnable
- Using Lambda (Java 8+)
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
// Usage
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
// Usage
Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
Thread thread2 = new Thread(new MyRunnable(), "Thread-2");
thread1.start();
thread2.start();
// Lambda expression
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
Thread thread = new Thread(task, "Lambda-Thread");
thread.start();
// Executor framework (preferred)
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(task);
executor.submit(task);
executor.shutdown();
21. Synchronization mechanismsโ
Answer:
class Counter {
private int count = 0;
// Synchronized method
public synchronized void increment() {
count++;
}
// Synchronized block
public void decrement() {
synchronized(this) {
count--;
}
}
// Static synchronization
private static int staticCount = 0;
public static synchronized void incrementStatic() {
staticCount++;
}
public int getCount() {
return count;
}
}
// Using locks (more flexible)
class CounterWithLock {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // Always unlock in finally
}
}
// Try lock with timeout
public boolean tryIncrement() {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return false;
}
}
22. Producer-Consumer problemโ
Answer:
class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 5;
private final Object lock = new Object();
// Producer
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (lock) {
while (queue.size() == capacity) {
lock.wait(); // Wait if queue is full
}
queue.offer(++value);
System.out.println("Produced: " + value);
lock.notifyAll(); // Notify consumers
}
Thread.sleep(1000);
}
}
// Consumer
public void consume() throws InterruptedException {
while (true) {
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait(); // Wait if queue is empty
}
int value = queue.poll();
System.out.println("Consumed: " + value);
lock.notifyAll(); // Notify producers
}
Thread.sleep(1500);
}
}
}
// Using BlockingQueue (easier)
class ProducerConsumerWithBlockingQueue {
private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
public void produce() throws InterruptedException {
int value = 0;
while (true) {
queue.put(++value); // Blocks if queue is full
System.out.println("Produced: " + value);
Thread.sleep(1000);
}
}
public void consume() throws InterruptedException {
while (true) {
int value = queue.take(); // Blocks if queue is empty
System.out.println("Consumed: " + value);
Thread.sleep(1500);
}
}
}
23. Thread pool and ExecutorServiceโ
Answer:
public class ThreadPoolExample {
public static void main(String[] args) {
// Different types of thread pools
ExecutorService fixedPool = Executors.newFixedThreadPool(3);
ExecutorService cachedPool = Executors.newCachedThreadPool();
ExecutorService singlePool = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
// Submit tasks
for (int i = 0; i < 10; i++) {
final int taskId = i;
fixedPool.submit(() -> {
System.out.println("Task " + taskId + " executed by " +
Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Scheduled tasks
scheduledPool.schedule(() -> {
System.out.println("Delayed task executed");
}, 5, TimeUnit.SECONDS);
scheduledPool.scheduleAtFixedRate(() -> {
System.out.println("Periodic task executed");
}, 0, 3, TimeUnit.SECONDS);
// Shutdown
fixedPool.shutdown();
try {
if (!fixedPool.awaitTermination(60, TimeUnit.SECONDS)) {
fixedPool.shutdownNow();
}
} catch (InterruptedException e) {
fixedPool.shutdownNow();
}
}
}
// Custom ThreadPoolExecutor
ThreadPoolExecutor customPool = new ThreadPoolExecutor(
2, // Core pool size
4, // Maximum pool size
60L, // Keep alive time
TimeUnit.SECONDS, // Time unit
new LinkedBlockingQueue<>(100), // Work queue
new ThreadPoolExecutor.CallerRunsPolicy() // Rejection policy
);
Java 8+ Featuresโ
24. Lambda expressions and functional interfacesโ
Answer:
// Functional interfaces
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
public class LambdaExample {
public static void main(String[] args) {
// Lambda expressions
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
Calculator subtract = (a, b) -> {
System.out.println("Subtracting " + b + " from " + a);
return a - b;
};
System.out.println(add.calculate(5, 3)); // 8
System.out.println(multiply.calculate(5, 3)); // 15
System.out.println(subtract.calculate(5, 3)); // 2
// Built-in functional interfaces
Predicate<String> isEmpty = String::isEmpty;
Function<String, Integer> length = String::length;
Consumer<String> print = System.out::println;
Supplier<String> supplier = () -> "Hello World";
// Usage
List<String> words = Arrays.asList("hello", "", "world", "");
words.stream()
.filter(isEmpty.negate()) // Remove empty strings
.map(length) // Get lengths
.forEach(System.out::println); // Print lengths
}
}
25. Stream API operationsโ
Answer:
public class StreamExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25, "Engineer"),
new Person("Bob", 30, "Manager"),
new Person("Charlie", 35, "Engineer"),
new Person("Diana", 28, "Designer")
);
// Intermediate operations (lazy)
List<String> engineerNames = people.stream()
.filter(p -> "Engineer".equals(p.getJob())) // Filter
.map(Person::getName) // Transform
.sorted() // Sort
.collect(Collectors.toList()); // Terminal operation
// More stream operations
OptionalDouble avgAge = people.stream()
.mapToInt(Person::getAge)
.average();
Map<String, List<Person>> byJob = people.stream()
.collect(Collectors.groupingBy(Person::getJob));
Map<String, Long> jobCounts = people.stream()
.collect(Collectors.groupingBy(
Person::getJob,
Collectors.counting()
));
// Parallel streams
List<Integer> numbers = IntStream.rangeClosed(1, 1000000)
.boxed()
.collect(Collectors.toList());
long sum = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
// Custom collectors
String names = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", ", "[", "]"));
}
}
26. Optional classโ
Answer:
public class OptionalExample {
public static void main(String[] args) {
// Creating Optional
Optional<String> optional1 = Optional.of("Hello");
Optional<String> optional2 = Optional.ofNullable(null);
Optional<String> optional3 = Optional.empty();
// Checking presence
if (optional1.isPresent()) {
System.out.println(optional1.get());
}
// Better approach
optional1.ifPresent(System.out::println);
// Default values
String value1 = optional2.orElse("Default");
String value2 = optional2.orElseGet(() -> "Computed Default");
// Transformations
Optional<Integer> length = optional1.map(String::length);
Optional<String> upper = optional1
.filter(s -> s.length() > 3)
.map(String::toUpperCase);
// Chaining
String result = findUser("john")
.flatMap(this::findAddress)
.map(Address::getCity)
.orElse("Unknown City");
}
// Methods returning Optional
public Optional<User> findUser(String name) {
// Database lookup
if ("john".equals(name)) {
return Optional.of(new User(name));
}
return Optional.empty();
}
public Optional<Address> findAddress(User user) {
// Address lookup
return Optional.ofNullable(user.getAddress());
}
}
JVM & Memory Managementโ
27. JVM memory structureโ
Answer:
public class MemoryStructure {
static int staticVar = 100; // Method Area (Metaspace in Java 8+)
public void demonstrateMemory() {
int localVar = 10; // Stack
String str = "Hello"; // String Pool (part of Heap)
Object obj = new Object(); // Heap
// Method call creates new stack frame
recursiveMethod(5);
}
public void recursiveMethod(int n) {
if (n <= 0) return;
int local = n; // Each call creates new stack frame
recursiveMethod(n - 1); // Stack grows with each recursive call
}
// Memory areas:
// 1. Heap: Objects, instance variables, arrays
// - Young Generation (Eden, S0, S1)
// - Old Generation (Tenured)
// 2. Stack: Method calls, local variables, partial results
// 3. Method Area/Metaspace: Class metadata, static variables
// 4. PC Register: Current executing instruction
// 5. Native Method Stack: Native method calls
}
28. Garbage collection typesโ
Answer:
public class GCExample {
public static void main(String[] args) {
// Different GC algorithms:
// 1. Serial GC (-XX:+UseSerialGC)
// Single-threaded, suitable for small applications
// 2. Parallel GC (-XX:+UseParallelGC) - Default in Java 8
// Multi-threaded, good for throughput
// 3. G1 GC (-XX:+UseG1GC) - Default in Java 9+
// Low-latency, suitable for large heaps
// 4. ZGC (-XX:+UseZGC) - Java 11+
// Ultra-low latency collector
// Memory leak example
List<String> memoryLeak = new ArrayList<>();
while (true) {
memoryLeak.add("Memory leak " + System.currentTimeMillis());
// This will eventually cause OutOfMemoryError
}
}
// Monitoring GC
public void monitorGC() {
// JVM flags for GC monitoring:
// -XX:+PrintGC
// -XX:+PrintGCDetails
// -XX:+PrintGCTimeStamps
// -Xloggc:gc.log
// Programmatic monitoring
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcBean : gcBeans) {
System.out.println("GC Name: " + gcBean.getName());
System.out.println("Collection Count: " + gcBean.getCollectionCount());
System.out.println("Collection Time: " + gcBean.getCollectionTime() + "ms");
}
}
}
29. Memory leaks and preventionโ
Answer:
public class MemoryLeakExamples {
// 1. Static collections holding references
private static List<Object> staticList = new ArrayList<>();
public void addToStaticList(Object obj) {
staticList.add(obj); // Objects never removed, potential leak
}
// 2. Listeners not removed
public class EventSource {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
// Missing remove method can cause leaks
public void removeListener(EventListener listener) {
listeners.remove(listener);
}
}
// 3. Inner class holding outer class reference
public class OuterClass {
private String data = "Large data";
public class InnerClass {
// Implicitly holds reference to OuterClass
public void doSomething() {
System.out.println("Inner class method");
}
}
// Better: use static inner class
public static class StaticInnerClass {
// No implicit reference to outer class
}
}
// 4. ThreadLocal not cleaned up
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void useThreadLocal() {
threadLocal.set("Some value");
// Should call threadLocal.remove() when done
}
// Prevention strategies
public void preventMemoryLeaks() {
// 1. Use weak references for caches
Map<String, Object> cache = new WeakHashMap<>();
// 2. Properly close resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
// Resource automatically closed
} catch (IOException e) {
e.printStackTrace();
}
// 3. Remove listeners
EventSource source = new EventSource();
EventListener listener = new EventListener() {};
source.addListener(listener);
// Later...
source.removeListener(listener);
// 4. Clean up ThreadLocal
threadLocal.set("value");
try {
// Use threadLocal
} finally {
threadLocal.remove(); // Important!
}
}
}
Design Patternsโ
30. Singleton pattern implementationsโ
Answer:
- Eager Initialization
- Lazy Initialization
- Double-Checked Locking
- Enum Singleton (Best)
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
// Private constructor
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("Doing something...");
}
}
// Usage: EnumSingleton.INSTANCE.doSomething();
31. Factory patternโ
Answer:
// Product interface
interface Shape {
void draw();
}
// Concrete products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Rectangle");
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Triangle");
}
}
// Factory class
class ShapeFactory {
public static Shape createShape(String shapeType) {
if (shapeType == null) return null;
switch (shapeType.toUpperCase()) {
case "CIRCLE":
return new Circle();
case "RECTANGLE":
return new Rectangle();
case "TRIANGLE":
return new Triangle();
default:
throw new IllegalArgumentException("Unknown shape: " + shapeType);
}
}
}
// Usage
public class FactoryPatternDemo {
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("CIRCLE");
Shape rectangle = ShapeFactory.createShape("RECTANGLE");
circle.draw(); // Drawing Circle
rectangle.draw(); // Drawing Rectangle
}
}
32. Observer patternโ
Answer:
import java.util.*;
// Subject interface
interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// Observer interface
interface Observer {
void update(String message);
}
// Concrete subject
class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String news;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(news);
}
}
public void setNews(String news) {
this.news = news;
notifyObservers();
}
}
// Concrete observers
class NewsChannel implements Observer {
private String name;
public NewsChannel(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " received news: " + news);
}
}
// Usage
public class ObserverPatternDemo {
public static void main(String[] args) {
NewsAgency agency = new NewsAgency();
NewsChannel cnn = new NewsChannel("CNN");
NewsChannel bbc = new NewsChannel("BBC");
agency.attach(cnn);
agency.attach(bbc);
agency.setNews("Breaking: Java 21 Released!");
// Output:
// CNN received news: Breaking: Java 21 Released!
// BBC received news: Breaking: Java 21 Released!
}
}
Coding Problemsโ
33. Reverse a string without using built-in methodsโ
Answer:
public class StringReverse {
// Method 1: Using char array
public static String reverse1(String str) {
if (str == null || str.length() <= 1) return str;
char[] chars = str.toCharArray();
int left = 0, right = chars.length - 1;
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
return new String(chars);
}
// Method 2: Using StringBuilder
public static String reverse2(String str) {
if (str == null || str.length() <= 1) return str;
StringBuilder sb = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
sb.append(str.charAt(i));
}
return sb.toString();
}
// Method 3: Recursive
public static String reverse3(String str) {
if (str == null || str.length() <= 1) return str;
return reverse3(str.substring(1)) + str.charAt(0);
}
public static void main(String[] args) {
String input = "Hello World";
System.out.println(reverse1(input)); // dlroW olleH
System.out.println(reverse2(input)); // dlroW olleH
System.out.println(reverse3(input)); // dlroW olleH
}
}
34. Find duplicate elements in arrayโ
Answer:
import java.util.*;
public class FindDuplicates {
// Method 1: Using HashSet
public static List<Integer> findDuplicates1(int[] arr) {
Set<Integer> seen = new HashSet<>();
Set<Integer> duplicates = new HashSet<>();
for (int num : arr) {
if (!seen.add(num)) {
duplicates.add(num);
}
}
return new ArrayList<>(duplicates);
}
// Method 2: Using HashMap for count
public static List<Integer> findDuplicates2(int[] arr) {
Map<Integer, Integer> countMap = new HashMap<>();
for (int num : arr) {
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
List<Integer> duplicates = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
if (entry.getValue() > 1) {
duplicates.add(entry.getKey());
}
}
return duplicates;
}
// Method 3: For array with elements 1 to n
public static List<Integer> findDuplicates3(int[] arr) {
List<Integer> duplicates = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
int index = Math.abs(arr[i]) - 1;
if (arr[index] < 0) {
duplicates.add(Math.abs(arr[i]));
} else {
arr[index] = -arr[index];
}
}
return duplicates;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 2, 5, 6, 3};
System.out.println(findDuplicates1(arr)); // [2, 3]
System.out.println(findDuplicates2(arr)); // [2, 3]
int[] arr2 = {4, 3, 2, 7, 8, 2, 3, 1};
System.out.println(findDuplicates3(arr2)); // [2, 3]
}
}
35. Implement LRU Cacheโ
Answer:
import java.util.*;
public class LRUCache<K, V> {
private final int capacity;
private final Map<K, Node<K, V>> cache;
private final Node<K, V> head;
private final Node<K, V> tail;
static class Node<K, V> {
K key;
V value;
Node<K, V> prev;
Node<K, V> next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
}
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new HashMap<>();
// Create dummy head and tail nodes
this.head = new Node<>(null, null);
this.tail = new Node<>(null, null);
head.next = tail;
tail.prev = head;
}
public V get(K key) {
Node<K, V> node = cache.get(key);
if (node == null) {
return null;
}
// Move to head (most recently used)
moveToHead(node);
return node.value;
}
public void put(K key, V value) {
Node<K, V> node = cache.get(key);
if (node != null) {
// Update existing node
node.value = value;
moveToHead(node);
} else {
// Add new node
Node<K, V> newNode = new Node<>(key, value);
if (cache.size() >= capacity) {
// Remove least recently used (tail)
Node<K, V> tail = removeTail();
cache.remove(tail.key);
}
cache.put(key, newNode);
addToHead(newNode);
}
}
private void addToHead(Node<K, V> node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(Node<K, V> node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(Node<K, V> node) {
removeNode(node);
addToHead(node);
}
private Node<K, V> removeTail() {
Node<K, V> lastNode = tail.prev;
removeNode(lastNode);
return lastNode;
}
// Test
public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>(2);
cache.put(1, "One");
cache.put(2, "Two");
System.out.println(cache.get(1)); // "One"
cache.put(3, "Three"); // Evicts key 2
System.out.println(cache.get(2)); // null
System.out.println(cache.get(3)); // "Three"
System.out.println(cache.get(1)); // "One"
}
}
36. Binary search implementationโ
Answer:
public class BinarySearch {
// Iterative binary search
public static int binarySearchIterative(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // Avoid overflow
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // Not found
}
// Recursive binary search
public static int binarySearchRecursive(int[] arr, int target) {
return binarySearchRecursive(arr, target, 0, arr.length - 1);
}
private static int binarySearchRecursive(int[] arr, int target, int left, int right) {
if (left > right) {
return -1; // Not found
}
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
return binarySearchRecursive(arr, target, mid + 1, right);
} else {
return binarySearchRecursive(arr, target, left, mid - 1);
}
}
// Find first occurrence
public static int findFirst(int[] arr, int target) {
int left = 0, right = arr.length - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
result = mid;
right = mid - 1; // Continue searching left
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
// Find last occurrence
public static int findLast(int[] arr, int target) {
int left = 0, right = arr.length - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
result = mid;
left = mid + 1; // Continue searching right
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 4, 4, 5, 6, 7};
System.out.println(binarySearchIterative(arr, 4)); // 3 (any occurrence)
System.out.println(binarySearchRecursive(arr, 4)); // 3 (any occurrence)
System.out.println(findFirst(arr, 4)); // 3 (first occurrence)
System.out.println(findLast(arr, 4)); // 5 (last occurrence)
System.out.println(binarySearchIterative(arr, 8)); // -1 (not found)
}
}
Quick Fire Questionsโ
37. What is the difference between JIT and JVM?โ
Answer: JVM executes bytecode, JIT (Just-In-Time) compiler optimizes frequently used bytecode to native machine code for better performance.
38. Can we override static methods?โ
Answer: No, static methods belong to class, not instance. They can be hidden but not overridden.
39. What is method hiding?โ
Answer: When a subclass defines a static method with same signature as parent class static method.
40. Difference between Heap and Stack memory?โ
Answer:
- Stack: Method calls, local variables, faster access, limited size
- Heap: Objects, instance variables, slower access, larger size, garbage collected
41. What is autoboxing and unboxing?โ
Answer:
Integer i = 10; // Autoboxing (int to Integer)
int j = i; // Unboxing (Integer to int)
42. What is the diamond problem?โ
Answer: Multiple inheritance ambiguity. Java solves it by not allowing multiple class inheritance, only multiple interface inheritance with default methods.
43. What is covariant return type?โ
Answer: Overriding method can return subtype of original return type.
class Parent {
Object getValue() { return new Object(); }
}
class Child extends Parent {
String getValue() { return "Hello"; } // String is subtype of Object
}
44. What is the difference between ArrayList and Vector?โ
Answer:
- ArrayList: Not synchronized, faster, allows null, introduced in Java 1.2
- Vector: Synchronized, slower, thread-safe, legacy class from Java 1.0
45. What is the difference between throw and throws?โ
Answer:
- throw: Used to explicitly throw an exception
- throws: Used in method signature to declare exceptions that method might throw
Performance Tips & Best Practicesโ
Best Practices:โ
- Use StringBuilder for string concatenation in loops
- Prefer ArrayList over Vector (unless thread safety needed)
- Use enhanced for loops when index not needed
- Initialize collections with capacity when size is known
- Use primitive collections (TIntList) for better performance
- Avoid creating unnecessary objects in loops
- Use static imports for frequently used static methods
- Implement equals() and hashCode() together
- Use try-with-resources for automatic resource management
- Prefer composition over inheritance
Common Mistakes to Avoid:โ
- Using == for string comparison
- Not overriding equals() and hashCode() together
- Creating objects in loops unnecessarily
- Not handling exceptions properly
- Using raw types instead of generics
- Memory leaks with listeners and static collections
- Not making utility classes final with private constructor
Summaryโ
This comprehensive Java interview guide covers:
Core Concepts:โ
- Java basics, OOP principles
- Memory management, JVM internals
- Exception handling strategies
Collections & Data Structures:โ
- List, Set, Map implementations
- Custom data structures (LRU Cache)
- Performance characteristics
Concurrency:โ
- Thread creation and synchronization
- Producer-consumer patterns
- Thread pools and ExecutorService
Modern Java:โ
- Lambda expressions and streams
- Optional class usage
- Functional programming concepts
Problem Solving:โ
- Algorithm implementations
- String manipulation
- Array processing techniques
Practice coding these examples and understand the reasoning behind each solution. Focus on explaining trade-offs and alternative approaches during interviews! ๐