Hello friends, in the last 6 months, I have attended more than 50 Java interviews across different companies, ranging from startups to large enterprises like investment banks like UBS, Citi, Morgan Stanley, and service based companies like Infosys and Wipro.
During this time, I noticed that certain questions were frequently repeated, covering Java 8 features, concurrency, multithreading, memory management, design patterns, collections, and general Java concepts.
In this article, I will share these commonly asked Java interview questions along with detailed answers.
These questions are suitable for any one from beginner to experienced level and I have seen it was asked to folks from 2 to 5 years experienced as well as senior folks having more than 5 to 10 years of experience.
1. Java 8 Features & Functional Programming
This is the hottest topic on Java interviews currently and I have increasingly see interviewer looking for candidates who knows how to write functional code in Java and well versed in lambda and Stream API.
Here are the frequently asked Java Stream and functional programming questions from those interviews:
1. What are the key features introduced in Java 8?
Java 8 introduced several major enhancements, including:
- Lambda Expressions — Enables functional programming in Java.
- Functional Interfaces — Interfaces with a single abstract method (e.g.,
Predicate
,Consumer
). - Stream API — Used for processing collections in a functional way.
- Optional Class — Helps avoid
NullPointerException
. - Default and Static Methods in Interfaces — Allows methods with implementations in interfaces.
- New Date and Time API — Improved API for handling date and time.
- Collectors API — Provides methods for grouping, joining, and partitioning collections.
2. How do you use lambda expressions in Java 8? Provide an example.
Lambda expressions provide a concise way to represent anonymous functions. You can also use Strategy pattern
Example:
Copy@FunctionalInterface
interface MathOperation {
int operation(int a, int b);
}
public class LambdaExample {
public static void main(String[] args) {
MathOperation addition = (a, b) -> a + b;
System.out.println("Sum: " + addition.operation(5, 10));
}
}
This eliminates the need for anonymous inner classes.
3. What is the difference between Predicate, Function, and Consumer interfaces?
These are functional interfaces in java.util.function
:
Here are the key differences between them:

Example:
CopyPredicate<Integer> isEven = x -> x % 2 == 0;
Function<Integer, String> convert = x -> "Number: " + x;
Consumer<String> print = System.out::println;
print.accept(convert.apply(10)); // Output: Number: 10
4. How does the Stream API work? Explain with an example.
The Stream API processes collections in a functional style.
Here is an example:
CopyList<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filtered = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
System.out.println(filtered); // Output: [Alice]
5. What is the difference between map()
and flatMap()
in Streams?
map()
– Transforms elements individually.flatMap()
– Flattens nested structures.
Example:
CopyList<List<String>> names = Arrays.asList(Arrays.asList("Alice", "Bob"), Arrays.asList("Charlie"));
List<String> flatMapped = names.stream().flatMap(List::stream).collect(Collectors.toList());
System.out.println(flatMapped); // Output: [Alice, Bob, Charlie]
6. How does the Optional
class work? How do you handle null values with it?
Optional
helps prevent NullPointerException
.
Example:
CopyOptional<String> name = Optional.ofNullable(null);
System.out.println(name.orElse("Default Name")); // Output: Default Name
7. What is the difference between forEach()
and peek()
in Streams?
forEach()
– Used for performing operations like printing.peek()
– Used for debugging without modifying elements.
Example:
CopyList<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream().peek(System.out::println).collect(Collectors.toList());
8 . How to Create Immutable Collections in Java 8?
You cannot use List.of() or Set.of() method in Java 8, they were added in Java 9.
Here is the way to create an immutable collections in Java.
CopyList<String> immutableList = Collections.unmodifiableList(Arrays.asList("a",
"b", "c"));
9. How Collectors.groupingBy()
Works
This method groups elements of a stream by a classifier.
CopyList<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Map<Integer, List<String>> grouped = names.stream()
.collect(Collectors.groupingBy(String::length));
10. What is the purpose of Default and Static Methods in Interfaces
Default methods Provide a default implementation for methods in interfaces. While static methods allow w defining utility methods in interfaces.
Copyinterface MyInterface {
default void print() {
System.out.println("Default Method");
}
static void utility() {
System.out.println("Static Method");
}
}
2. Concurrency & Multithreading
This is another topic which is quite popular on Java Interviews, here are the common Java Interview Questions from Concurrency and Multithreading
1. What are the differences between synchronized
, Lock
, and ReentrantLock
?
synchronized
– Intrinsic lock at the method or block level.Lock
– More flexible, can try acquiring locks without blocking.ReentrantLock
– Allows recursive locking by the same thread.
Example:
CopyLock lock = new ReentrantLock();
lock.lock();
try {
System.out.println("Locked operation");
} finally {
lock.unlock();
}
2. How does the CompletableFuture
API work?
It allows asynchronous programming without blocking.
Example:
CopyCompletableFuture.supplyAsync(() -> "Hello, World!")
.thenAccept(System.out::println);
3. What is the difference between Callable
and Runnable
?
Runnable
– Does not return a result.Callable
– Returns a result and can throw checked exceptions.
Example:
CopyCallable<Integer> task = () -> 10 * 2;
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> result = executor.submit(task);
4. Explain the Fork/Join framework and its advantages.
The Fork/Join framework is a parallel processing framework introduced in Java 7 designed for recursive divide-and-conquer algorithms.
Key Features:
- Divides a task into smaller subtasks until they’re simple enough to be processed
- Uses a work-stealing algorithm where idle threads “steal” tasks from busy threads
- Based on the
ForkJoinPool
executor that implements work-stealing
Main Components:
ForkJoinPool
: Special thread pool for executing ForkJoinTasksForkJoinTask
: Abstract base class for tasks that run within aForkJoinPool
RecursiveTask
: A ForkJoinTask that returns a resultRecursiveAction
: A ForkJoinTask with no result (void)
Example — Parallel Sum of an Array:
Copyclass SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000;
private final long[] array;
private final int start;
private final int end;
public SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
if (length <= THRESHOLD) {
// Sequential processing for small enough tasks
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
// Split task for parallel processing
int mid = start + length / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
// Fork right subtask
right.fork();
// Compute left subtask
long leftResult = left.compute();
// Join right subtask
long rightResult = right.join();
// Combine results
return leftResult + rightResult;
}
}
}
// Usage
long[] numbers = new long[100000];
// Fill array with numbers
ForkJoinPool pool = new ForkJoinPool();
long sum = pool.invoke(new SumTask(numbers, 0, numbers.length));
Now, if you are wondering why use ForkJoinPool in Java then let me tell about the advantages:
Fork Join Pool:
- Efficiently utilizes multiple processors
- Automatically balances workload through work-stealing
- Simplified parallel programming model
- Good for CPU-intensive tasks with divide-and-conquer approach
- Handles subtask scheduling internally
5. What is the difference between parallelStream() and Stream()?
Stream():
- Processes elements sequentially in a single thread
- Preserves order of elements
- Deterministic results for stateful operations
- Typically faster for small data sets or when operations aren’t CPU-intensive
- Less overhead as no thread coordination is required
parallelStream():
- Processes elements in parallel using multiple threads from the common ForkJoinPool
- May not preserve encounter order unless explicitly required
- Results of stateful operations may be non-deterministic
- Better performance for large data sets with CPU-intensive operations
- Has overhead for splitting data, coordinating threads, and combining results
Example comparing both:
Copy// Sequential stream
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
long sequentialSum = numbers.stream()
.filter(n -> n % 2 == 0)
.mapToLong(Integer::longValue)
.sum();
// Parallel stream
long parallelSum = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.mapToLong(Integer::longValue)
.sum();
When to use parallelStream():
- Large data sets where overhead is justified
- CPU-intensive operations
- No mutable shared state between operations
- When operations are stateless and associative
- When hardware has multiple cores/processors
When to use stream():
- Small to medium data sets
- I/O-bound operations
- When order is critical
- When operations have side effects or shared mutable state
- When operations are not easily parallelizable

5. Can you Explain the significance of the volatile keyword.
Volatile is a keyword in Java used for fields that addresses visibility issues in multithreaded environments.
Key aspects:
- Memory Visibility: Guarantees that reads and writes to a volatile variable will be visible to all threads. When a thread updates a volatile variable, all other threads will see the updated value immediately.
- Preventing Reordering: Prevents the compiler and JVM from reordering instructions involving the volatile variable, acting as a lightweight memory barrier.
- No Atomicity: Does NOT guarantee atomicity for compound operations. For example, i++ is not atomic even if i is volatile.
- No Mutual Exclusion: Does not provide locking or mutual exclusion.
Use cases:
- Flag variables that indicate state changes (e.g., a “done” flag in producer-consumer)
- Simple status variables that don’t require atomicity
- Double-checked locking pattern (in conjunction with synchronized)
Example:
Copypublic class SharedFlag {
private volatile boolean flag = false;
// Thread 1 runs this method
public void setFlag() {
flag = true;
// After this line, all threads will see flag as true
}
// Thread 2 runs this method
public void checkFlag() {
// This will see the updated value of flag
while (!flag) {
// Wait until flag becomes true
}
// Proceed when flag is true
}
}
Limitations:
- Not suitable for operations that require atomicity
- For compound actions, consider using AtomicInteger, AtomicBoolean, etc.
- For more complex thread synchronization, use locks or higher-level concurrency utilities
The JMM (Java Memory Model)
guarantees that when a thread writes to a volatile variable, it not only makes that write visible to other threads but also makes all the writes that happened before the volatile write visible.
3. Memory Management & Performance
If you are going for an experienced Java developer role then this is one topic where you can expect to be grilled on your interview. I have seen many questions on this topic especially around GC.
1. How does Java handle memory management, and what are the different garbage collectors?
Java uses automatic garbage collection. Common GC algorithms:
- Serial GC — Best for small applications.
- Parallel GC — Uses multiple threads.
- G1 GC — Focuses on low latency.
- ZGC — Scalable and low-pause garbage collector.
2. What is the difference between WeakReference
, SoftReference
, and PhantomReference
?
Reference TypeCollected When?SoftReference
When memory is lowWeakReference
When GC runsPhantomReference
Before object is finalized
4. Design Patterns & Best Practices
This is another topic which is quite popular on Java Interviews, particularly the GOF patterns and SOLID principles.
Here are the frequently asked questions on these topics:
1. What are the key principles of SOLID design?
- S — Single Responsibility
- O — Open/Closed
- L — Liskov Substitution
- I — Interface Segregation
- D — Dependency Inversion
2. Can you explain the Strategy and Factory design patterns with examples?
- Strategy Pattern:
- Defines a family of algorithms, encapsulates each one, and makes them interchangeable
- Lets the algorithm vary independently from the clients that use it
- Factory Pattern — Creates objects without exposing instantiation logic.
Example:
Copy// Strategy interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete strategies
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit card " + cardNumber);
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal account " + email);
}
}
// Context
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
// Usage
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456"));
cart.checkout(100);
cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
cart.checkout(200);
Factory Pattern:
- Creates objects without exposing the instantiation logic
- Refers to the newly created object through a common interface
Here is example of factory pattern in Java:
Copy// Product interface
interface Vehicle {
void drive();
}
// Concrete products
class Car implements Vehicle {
@Override
public void drive() {
System.out.println("Driving a car");
}
}
class Motorcycle implements Vehicle {
@Override
public void drive() {
System.out.println("Riding a motorcycle");
}
}
// Factory
class VehicleFactory {
public static Vehicle createVehicle(String type) {
if ("car".equalsIgnoreCase(type)) {
return new Car();
} else if ("motorcycle".equalsIgnoreCase(type)) {
return new Motorcycle();
}
throw new IllegalArgumentException("Unknown vehicle type");
}
}
// Usage
Vehicle car = VehicleFactory.createVehicle("car");
car.drive();
Vehicle motorcycle = VehicleFactory.createVehicle("motorcycle");
motorcycle.drive();
3. How does Dependency Injection work in Java?
Dependency Injection (DI) is a design pattern where dependencies are “injected” into a class rather than the class creating them.
Key Concepts:
- Instead of creating dependencies within a class, they are provided from the outside
- Reduces coupling between classes
- Improves testability by allowing mock dependencies
- Enhances modularity and reuse
Types of Dependency Injection:
- Constructor Injection: Dependencies provided through a constructor
- Setter Injection: Dependencies provided through setter methods
- Field Injection: Dependencies injected directly into fields (typically using annotations)
Example using Spring Framework:
Copy// Service interface
interface UserService {
User findById(Long id);
}
// Service implementation
@Service
class UserServiceImpl implements UserService {
private final UserRepository userRepository;
// Constructor injection
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
}
// Controller using the service
@RestController
class UserController {
private final UserService userService;
// Constructor injection
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
5. Java Collections Framework
Since Collections are very common on any Java application, its also quite popular on Java interviews. Here are the frequently asked questions on Java Collection framework from my past Java Interviews.
1. What is the difference between HashMap
and ConcurrentHashMap
?
HashMap
– Not thread-safe.ConcurrentHashMap
– Thread-safe, allows concurrent modifications.
2. How does TreeMap
maintain order?
It sorts keys based on natural orderingor a custom comparator.
4. What are the differences between ArrayList, LinkedList, and CopyOnWriteArrayList?
Here are the key differences between ArrayList, LinkedList, and CopyOnWriteArrayList in Java:
ArrayList:
- Backed by a dynamic array
- Fast random access (O(1)) using index
- Slow insertions/deletions in the middle (O(n)) as elements need to be shifted
- Not thread-safe
- Good for scenarios with frequent access but infrequent modifications
LinkedList:
- Implemented as a doubly-linked list
- Slow random access (O(n)) as it requires traversal
- Fast insertions/deletions in the middle (O(1)) once position is found
- Not thread-safe
- Uses more memory than
ArrayList
due to storage of pointers - Good for frequent insertions/deletions but infrequent access by index
CopyOnWriteArrayList:
- Thread-safe implementation
- Creates a new copy of the underlying array when modified
- Very expensive writes
(O(n)
) as the entire array is copied - Fast reads that never block
- Iterator doesn’t support remove operations and doesn’t reflect modifications after creation
- Best for collections that are primarily read with very infrequent updates
6. Core Java
Now, let’s see few frequently asked questions from core Java topics. This is where they asked about java.lang
package and various Java programming features. Here are the frequently asked questions from Core Java topic from last 6 months.
1. How do equals()
and hashCode()
work in Java?
equals()
– Compares object values.hashCode()
– Returns a unique identifier used in collections likeHashMap
.
Example:
Copy@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person person = (Person) obj;
return this.name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
Resources I used to Prepare for Java Interviews
Apart from reading blogs and articles on Medium and reading Java documentations here are resources I have used to prepare for my interviews:
Grokking the Java Interview: Prepare for Java interviews by learning essential Core Java concepts…
Cracking the Java Interview is not easy, and one of the main reasons for that is Java is very vast. There are a lot of…
Grokking the Java Interview – Volume 2
Welcome to the second volume of “Grokking the Java Interview.” This book is a continuation of the first volume, which…
2. Java Multithreading for Senior Engineering Interviews (Course)
Java Multithreading for Senior Engineering Interviews
Level up your coding skills. No more passive learning. Interactive in-browser environments keep you engaged and test…
3. Ace the Java Coding Interview(course)
Ace the Java Coding Interview – AI-Powered Learning for Developers
Java is a general-purpose programming language designed to be highly portable across different platforms and operating…
4. Algomonster — For data structures and algorithms questions

Conclusion
If you’re preparing for Java interviews, focus on Java 8 features, concurrency, memory management, collections, and design patterns. Understanding these core topics will help you succeed in technical discussions and coding assessments.
“Algomonster“, “Grokking the Java Interview” and Java Multithreading for Senior Engineering Interviews were my companions on this journey and helping me conquer the challenges of Java interviews.