In Java, there are 3 main ways (plus modern variations) to create and run threads.
I’ll explain
each one in depth, with when to use, pros/cons, and examples.
1️⃣ Extending Thread class (Classic & Simple)
How it works
You create a class that extends Thread and override the run() method.
Example
class MyThread extends Thread {
@Override
public void run() {
[Link]("Thread running: " +
[Link]().getName());
}
}
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();
[Link](); // IMPORTANT: start(), not run()
}
}
Key points
● start() creates a new thread and calls run()
● Calling run() directly runs in the main thread
Pros
✔ Very simple
✔ Easy to understand for beginners
Cons
❌ Java allows only single inheritance
❌ Tight coupling with thread logic
When to use
● Simple demos
● Learning basics
2️⃣ Implementing Runnable (Most Common &
Recommended)
How it works
You implement Runnable and pass it to a Thread object.
Example
class MyTask implements Runnable {
@Override
public void run() {
[Link]("Runnable running: " +
[Link]().getName());
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new MyTask());
[Link]();
}
}
Pros
✔ Supports multiple inheritance
✔ Separates task from thread
✔ Cleaner design
Cons
❌ No return value
❌ No checked exception handling
When to use
● Most real-world applications
● When you just want to run logic in parallel
3️⃣ Using Lambda Expression (Runnable shortcut)
How it works
Since Runnable is a functional interface, you can use a lambda.
Example
Thread t1 = new Thread(() -> {
[Link]("Lambda thread running");
doWork();
});
[Link]();
Pros
✔ Very concise
✔ Modern Java style
Cons
❌ Can reduce readability if logic is large
When to use
● Short tasks
● Clean, modern code
4️⃣ Implementing Callable + Future (When you need
return value)
How it works
Callable returns a value and can throw checked exceptions.
You submit it to an ExecutorService.
Example
Callable<String> task = () -> {
[Link](1000);
return "Done";
};
ExecutorService executor = [Link]();
Future<String> future = [Link](task);
String result = [Link](); // waits for result
[Link]();
Pros
✔ Returns a value
✔ Can throw checked exceptions
✔ Better control
Cons
❌ Slightly complex
When to use
● When you need results from threads
5️⃣ Using ExecutorService (Best Practice for Production)
How it works
Thread pool manages threads instead of you creating them manually.
Example
ExecutorService executor = [Link](3);
[Link](() -> {
[Link]("Task executed by pool");
});
[Link]();
Pros
✔ Thread reuse (high performance)
✔ Controlled thread creation
✔ Avoids memory issues
Cons
❌ Must manage lifecycle (shutdown())
When to use
● Almost all production systems
6️⃣ Scheduled Threads (ScheduledExecutorService)
Example
ScheduledExecutorService scheduler =
[Link](1);
[Link](() ->
[Link]("Runs after 5 seconds"),
5, [Link]);
Use cases
● Cron jobs
● Polling tasks
● Delayed execution
7️⃣ Virtual Threads (Java 21+ – Advanced)
Example
[Link](() -> {
[Link]("Virtual thread running");
});
Pros
✔ Lightweight
✔ Millions of threads possible
When to use
● High concurrency systems
● I/O heavy apps
🔁 Summary Table
Method Return Value Best Use
Extends Thread ❌ Learning
Runnable ❌ General purpose
Lambda Runnable ❌ Short tasks
Callable + Future ✔ Result needed
ExecutorService ❌/✔ Production
ScheduledExecuto ❌ Scheduled jobs
r
Virtual Threads ❌ High concurrency
Why Runnable cannot throw checked exceptions
1️⃣ Look at the Runnable interface definition
@FunctionalInterface
public interface Runnable {
void run();
}
Key point
The run() method:
● Returns void
● Does NOT declare throws Exception
2️⃣ Java rule: Checked exceptions must be declared or
handled
Java enforces this rule at compile time:
If a method can throw a checked exception, it must declare it in its throws clause.
Since [Link]() does not declare any checked exceptions:
● Your implementation cannot throw checked exceptions
● Compiler error occurs if you try
❌ Example (Compile-time error)
Runnable r = () -> {
[Link](1000); // Checked exception
};
❌ Error:
Unhandled exception: [Link]
3️⃣ Why Java designers made Runnable this way
🔹 Reason 1: Simplicity
Runnable was designed (Java 1.0) to be:
● Simple
● Lightweight
● Easy to use
Allowing checked exceptions would:
● Complicate thread APIs
● Force callers to handle unknown exceptions
🔹 Reason 2: Thread execution boundary
Threads run asynchronously.
If a checked exception occurs:
● There is no caller to catch it
● The thread just terminates
So checked exceptions don’t make sense across thread boundaries.
🔹 Reason 3: API stability
If Runnable allowed checked exceptions:
● Every method that accepts Runnable
would need throws Exception
● That would break a massive amount of existing code
4️⃣ How Callable solves this problem
Callable definition
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Key differences
Runnable Callable
void run() V call()
No checked Allows checked exceptions
exceptions
Fire-and-forget Returns value
No error propagation Exception via Future
5️⃣ How checked exceptions are handled in Callable
Future<Integer> future = [Link](() -> {
if (true) {
throw new IOException("Failure");
}
return 1;
});
[Link](); // throws ExecutionException
● Checked exception is wrapped inside ExecutionException
● Caller explicitly handles it
6️⃣ Workarounds with Runnable (not recommended)
Option 1: Try-catch inside run()
Runnable r = () -> {
try {
[Link](1000);
} catch (InterruptedException e) {
[Link]().interrupt();
}
};
Option 2: Convert to unchecked exception
throw new RuntimeException(e);
⚠️ This hides real failure causes.
7️⃣ Interview-ready explanation (short)
Runnable cannot throw checked exceptions because its run() method does not
declare a throws clause, and Java enforces checked exception handling at
compile time. Since threads run asynchronously with no caller to handle checked
exceptions, Java introduced Callable for tasks that need return values or
checked exception propagation.
8️⃣ One-line summary
Runnable is for fire-and-forget tasks; Callable is for tasks that return results
or throw checked exceptions.
This is a core Java concurrency question. I’ll explain it in three layers:
1. What is the difference
2. How each works internally
3. When and why to use each (real-world & interview view)
1️⃣ Difference: Thread vs Runnable
Aspect Extending Thread Implementing
Runnable
What you create A Thread itself A task (job)
Inheritance ❌ Blocks extending another ✔ Can extend any class
class
Design Tight coupling Loose coupling
Reusability Low High
Best practice ❌ Not recommended ✔ Recommended
Return value ❌ ❌
Checked ❌ ❌
exceptions
2️⃣ How they work internally (MOST
IMPORTANT)
Key concept (very important)
Java always executes a thread by calling [Link]() internally
Runnable never creates a thread on its own.
🔹 Case 1: Extending Thread
Code
class MyThread extends Thread {
@Override
public void run() {
[Link]("Running MyThread");
}
}
MyThread t = new MyThread();
[Link]();
🔍 Internal execution flow
[Link]()
↓
JVM creates new OS thread
↓
JVM calls [Link]()
↓
[Link]() executes
Important detail
● run() belongs to Thread
● You override it
● Logic lives inside the thread object
❌ Design problem
Thread = Task + Execution
They are tightly bound.
🔹 Case 2: Implementing Runnable
Code
class MyTask implements Runnable {
@Override
public void run() {
[Link]("Running MyTask");
}
}
Thread t = new Thread(new MyTask());
[Link]();
🔍 Internal execution flow
[Link]()
↓
JVM creates new OS thread
↓
JVM calls [Link]()
↓
[Link]() calls [Link]()
↓
[Link]() executes
This is the key difference internally
Inside Thread class (simplified)
public void run() {
if (target != null) {
[Link](); // Runnable's run()
}
}
✔ Clean separation
Runnable → WHAT to do
Thread → HOW to run
3️⃣ Memory & Object Model Difference
Thread approach
MyThread object
├─ Thread metadata
└─ Business logic
Runnable approach
Thread object
└─ Runnable reference → Business logic
Runnable = lighter & reusable
4️⃣ Why Runnable is better (Deep Reason)
1️⃣ Separation of concerns
● Thread → execution mechanism
● Runnable → task logic
2️⃣ Supports multiple inheritance
class OrderService extends BaseService implements Runnable
3️⃣ Executor framework compatibility
[Link](new MyTask());
Executor works with Runnable, not Thread.
5️⃣ Common mistake (Interview favorite)
❌ Wrong
[Link](); // NO new thread
✔ Correct
[Link](); // Creates new thread
6️⃣ When to use what
✔ Use Thread when:
● Learning Java threads
● Simple experiments
● Debugging thread behavior
✔ Use Runnable when:
● Writing real applications
● Using thread pools
● Sharing logic across threads
7️⃣ Interview-ready explanation (short)
Extending Thread combines the task and thread into one object, whereas
implementing Runnable separates the task from the thread. Internally,
[Link]() always calls [Link](), and when a Runnable is passed,
[Link]() delegates execution to [Link](). This separation makes
Runnable more flexible, reusable, and suitable for Executor-based concurrency.
8️⃣ One-line summary
Thread creates the execution path; Runnable supplies the work to execute.
Here’s a clear + in-depth explanation of Comparable vs Comparator in Java, with
examples, internal working, and interview tips.
Comparable vs Comparator in Java
Both are used to define sorting logic, but where and how that logic is written is the key
difference.
1️⃣ Comparable (Natural Ordering)
📌 What it is
● Interface in [Link]
● Used to define natural/default order of objects
● Implemented inside the class
public interface Comparable<T> {
int compareTo(T o);
}
🧠 How it works internally
● Sorting methods ([Link], [Link]) check:
if (obj instanceof Comparable)
● Then they call:
[Link](obj2)
✅ Example
class Employee implements Comparable<Employee> {
int id;
String name;
Employee(int id, String name) {
[Link] = id;
[Link] = name;
}
@Override
public int compareTo(Employee e) {
return [Link] - [Link]; // ascending by id
}
}
[Link](employeeList);
⚠️ Limitation
● Only one sorting logic
● Changing order = modify class ( ❌ not flexible)
2️⃣ Comparator (Custom Ordering)
📌 What it is
● Interface in [Link]
● Used for multiple / external sorting strategies
● Implemented outside the class
public interface Comparator<T> {
int compare(T o1, T o2);
}
🧠 How it works internally
● Sorting method calls:
[Link](o1, o2)
● No change required in the target class
✅ Example
Comparator<Employee> nameComparator = (e1, e2) ->
[Link]([Link]);
[Link](employeeList, nameComparator);
3️⃣ Key Differences (Very Important)
Feature Comparable Comparator
Package [Link] [Link]
Method compareTo() compare()
Defined Inside class Outside
class
Sorting logic Single (natural) Multiple
Modifies class Yes No
Lambda ❌ ✅
support
Flexibility Low High
4️⃣ Multiple Comparators Example
Comparator<Employee> byId =
[Link](e -> [Link]);
Comparator<Employee> byName =
[Link](e -> [Link]);
Comparator<Employee> byIdThenName =
[Link](byName);
5️⃣ Java 8+ Comparator Utilities (Very Important)
🔹 Reverse Order
Comparator<Employee> descId =
[Link](Employee::getId).reversed();
🔹 Null-safe
Comparator<Employee> safe =
[Link](
[Link](Employee::getName)
);
6️⃣ What if both are present?
class Employee implements Comparable<Employee> {
public int compareTo(Employee e) { ... }
}
[Link](list); // uses Comparable
[Link](list, comparator); // uses Comparator
👉 Comparator always overrides Comparable when provided.
7️⃣ When to use what? (Interview Gold ⭐)
Use Comparable when:
● Class has one natural order
● Sorting logic is intrinsic to the object
Use Comparator when:
● Need multiple sorting rules
● Cannot modify the class
● Sorting logic is context-specific
8️⃣ Real-world analogy 🧠
● Comparable → “Default behavior”
● Comparator → “Custom rule for this situation”
9️⃣ Common Mistakes ⚠️
❌ Using subtraction for compareTo (overflow risk)
return [Link] - [Link];
✅ Correct
return [Link]([Link], [Link]);
🔥 Interview One-Liner
Comparable defines the natural ordering inside the class, while Comparator
provides external, multiple, and flexible sorting strategies.
If you want next:
● Sorting with Streams
● Comparable vs Comparator internals
● TreeSet / TreeMap behavior
● How sorting works in TimSort
Comparable vs Comparator internals
Great question 👍
Let’s go deep into internals — how Comparable and Comparator are actually used inside
Java’s sorting & collections.
Comparable vs Comparator — Internals
Explained
1️⃣ Where sorting actually happens
When you call:
[Link](list);
or
[Link](array);
Java does not sort by itself in Comparable or Comparator.
👉 Sorting is done by TimSort (for objects).
2️⃣ Internals of [Link]()
Case 1: Using Comparable
[Link](list);
Internally (simplified):
public static <T extends Comparable<? super T>> void sort(List<T>
list) {
Object[] a = [Link]();
[Link](a); // calls Comparable
// copy back to list
}
Inside [Link]():
if (obj instanceof Comparable) {
((Comparable)obj1).compareTo(obj2);
}
👉 Key point
● JVM assumes objects know how to compare themselves
● Calls compareTo() repeatedly during sorting
3️⃣ Internals of [Link](list, comparator)
Case 2: Using Comparator
[Link](list, comparator);
Internally:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
Object[] a = [Link]();
[Link](a, c); // comparator-driven
}
Comparator-based comparison:
[Link](o1, o2);
👉 The object itself is never involved in comparison logic.
4️⃣ Core Internal Difference (Very Important)
Aspect Comparable Comparator
Who owns comparison logic Object itself External object
Who decides order [Link]() [Link]()
Dependency Object must implement Object unaware of sorting
interface
Sorting algorithm Same (TimSort) Same (TimSort)
Comparison call [Link](obj2) [Link](obj1,
obj2)
5️⃣ How TimSort uses them internally
TimSort repeatedly compares elements:
if (compare(a[i], a[j]) <= 0) {
// order ok
}
Comparison resolution:
if (comparator != null)
[Link](a, b);
else
((Comparable)a).compareTo(b);
👉 Only the comparison source changes, not the algorithm.
6️⃣ TreeSet / TreeMap Internals (Critical ⭐)
TreeSet uses Red-Black Tree
Constructor:
TreeSet<T> set = new TreeSet<>();
TreeSet<T> set = new TreeSet<>(comparator);
Internal logic:
if (comparator != null)
[Link](k1, k2);
else
((Comparable)k1).compareTo(k2);
🔥 IMPORTANT
● TreeSet uses comparison for:
○ Sorting
○ Uniqueness
● If compare() returns 0, element is considered duplicate
👉 Even if equals() returns false!
7️⃣ Comparable vs equals() Contract
Comparable contract:
[Link](y) == 0
should imply:
[Link](y) == true
❌ Breaking this causes:
● Missing elements in TreeSet
● Unexpected overwrites in TreeMap
8️⃣ Why Comparator is more powerful internally
1️⃣ Multiple orderings
Comparator<Employee> byId;
Comparator<Employee> byName;
2️⃣ Runtime decision
Comparator<Employee> comp =
sortByName ? byName : byId;
3️⃣ Stateless
● No modification of domain class
● Cleaner architecture
9️⃣ Lambda & Method References (Internal Advantage)
Comparator<Employee> c =
[Link](Employee::getSalary);
Internally:
[Link](e1, e2);
Clean, reusable, efficient.
🔟 Performance Internals
Factor Comparable Comparator
Method call overhead Slightly less Slightly more
JVM inlining Easier Slightly harder
Real-world impact Negligible Negligible
👉 Performance difference is irrelevant in practice.
🔥 Interview-Level Summary
Comparable embeds comparison logic inside the object and sorting calls
compareTo() directly, whereas Comparator externalizes the comparison and
sorting frameworks delegate ordering decisions to the provided comparator.
Internally, both feed comparison results into the same sorting algorithm (TimSort or
Red-Black Tree).
Lambda Expression vs Anonymous Class
(Java)
1️⃣ What is an Anonymous Class?
An anonymous class is a class without a name that you define and instantiate at the same
time.
Example
Runnable r = new Runnable() {
@Override
public void run() {
[Link]("Running");
}
};
✔ Creates a new class at runtime
✔ Can have fields, methods, constructors (via init block)
2️⃣ What is a Lambda Expression?
A lambda expression is a compact way to represent a function.
Example
Runnable r = () -> {
[Link]("Running");
};
✔ No class creation in source code
✔ Works only with functional interfaces
3️⃣ BIGGEST DIFFERENCE (Core Concept)
Aspect Anonymous Class Lambda
Represent A class A function
s
Based on OOP Functional programming
Requires Interface / abstract Functional interface
class only
4️⃣ Internal Working (Very Important ⭐)
Anonymous Class — Internals
new Runnable() { ... }
Compiler generates a separate .class file
Employee$[Link]
●
● Each instance is a real object
● Has its own this
Lambda — Internals
() -> [Link]("Running");
● No extra class file
● Uses invokedynamic
● JVM creates function object only when needed
● Often reused (stateless lambdas)
5️⃣ this keyword difference (Very common question)
Anonymous Class
this // refers to anonymous class instance
Lambda
this // refers to enclosing class
6️⃣ Variable capture (effectively final)
Anonymous Class
int x = 10;
new Runnable() {
public void run() {
// x must be final
}
};
Lambda
int x = 10;
Runnable r = () -> {
// x must be effectively final
};
✔ Same rule, but lambdas feel cleaner
7️⃣ Can they have state?
Anonymous Class ✅ YES
new Runnable() {
int count = 0;
};
Lambda ❌ NO fields
● Lambdas cannot declare instance variables
● Stateless by design
8️⃣ Performance differences
Aspect Anonymou Lambda
s
Class creation Yes No
Memory More Less
JVM optimization Limited Better
Reusability Poor High
👉 Lambdas are generally more efficient.
9️⃣ Multiple methods support
Anonymous Class ✅
new Thread(new WindowAdapter() {
public void windowOpened() {}
public void windowClosed() {}
});
Lambda ❌
Only one abstract method allowed.
🔟 When to use what?
Use Lambda when:
● Functional interface
● Short logic
● Behavior passing
● Streams, threads, executors
Use Anonymous Class when:
● Multiple methods needed
● Need state
● Extending abstract class
● Legacy code
🔥 Interview-ready summary
An anonymous class creates a real class instance with its own this, whereas a
lambda expression represents a function and does not create a separate class.
Lambdas work only with functional interfaces and are more concise,
memory-efficient, and better optimized by the JVM.
Quick one-line comparison
Anonymous class = object-oriented
Lambda = functional
PART 1️⃣ Lambda Internals —
invokedynamic (Deep but simple)
Before Java 8 (Anonymous classes)
Runnable r = new Runnable() {
public void run() {
[Link]("Hello");
}
};
What compiler did ❌
Generated a new .class file
Main$[Link]
●
● Each anonymous class = separate class
● Heavy on class loading + memory
Java 8+ Lambdas (BIG CHANGE)
Runnable r = () -> [Link]("Hello");
Compiler behavior ✅
● NO extra class file
● Generates bytecode with invokedynamic
● Defers object creation to runtime JVM
What is invokedynamic?
A JVM instruction that lets the JVM decide at runtime how a method call should be
linked.
Instead of hard-coding:
new Runnable() { ... }
Java generates:
invokedynamic run()
Runtime Flow (Very Important)
Step-by-step
1. JVM encounters invokedynamic
2. JVM calls LambdaMetafactory
3. JVM dynamically creates:
○ A lightweight function object
○ Bound to the functional interface
4. JVM may:
○ Cache it
○ Reuse it
○ Inline it
👉 Much faster + memory efficient
Stateless vs Stateful Lambdas
Stateless lambda
Runnable r = () -> [Link]("Hi");
✔ JVM creates ONE instance
✔ Reused everywhere
Stateful lambda
int x = 10;
Runnable r = () -> [Link](x);
✔ New instance per capture
✔ Still lighter than anonymous class
Why lambdas are faster
Reason Benefit
No class file Faster startup
invokedynamic Late binding
JVM inlining Better optimization
Instance reuse Less GC
🔥 Interview line
Lambdas use invokedynamic to defer class creation to runtime, allowing the JVM
to optimize, inline, and reuse lambda instances instead of generating separate
class files like anonymous classes.
PART 2️⃣ Lambda vs Method Reference
What is a Method Reference?
A method reference is a shortcut to a lambda that only calls a method.
Lambda
[Link](e -> [Link](e));
Method Reference
[Link]([Link]::println);
✔ Same behavior
✔ Cleaner syntax
Types of Method References (4 types)
1️⃣ Static method
ClassName::staticMethod
Integer::parseInt
2️⃣ Instance method (object)
object::instanceMethod
[Link]::println
3️⃣ Instance method (class)
ClassName::instanceMethod
String::toLowerCase
Equivalent lambda:
s -> [Link]()
4️⃣ Constructor reference
ClassName::new
Employee::new
Equivalent lambda:
() -> new Employee()
Internal difference? ❌ NONE
Lambda and method reference compile the same way.
Both use:
invokedynamic
LambdaMetafactory
No performance difference.
When to use what?
Use Method Reference when:
● Lambda only calls one existing method
● Improves readability
Use Lambda when:
● Logic is more than one step
● Needs condition or transformation
this behavior (important)
Feature Lambda Method
Reference
this Enclosing Enclosing class
class
New object ❌ ❌
Both behave identically.
Common Interview Trap ❗
“Method references are faster than lambdas”
❌ False
They are just syntax sugar.
Final Interview Summary (Gold ⭐)
Lambdas are implemented using the invokedynamic bytecode instruction,
allowing the JVM to dynamically create and optimize function objects at runtime.
Method references are a shorthand form of lambdas and compile to the same
invokedynamic mechanism, with no performance difference.
Why Lambda Cannot Throw Checked
Exceptions
Short answer (interview line)
A lambda expression cannot throw checked exceptions unless the functional
interface method explicitly declares them, because a lambda is just an
implementation of that method and must obey its signature.
1️⃣ What a lambda really is (important)
This lambda:
Runnable r = () -> {
throw new IOException();
};
Is NOT a special construct.
It is equivalent to:
class MyRunnable implements Runnable {
public void run() {
throw new IOException(); // ❌ compile error
}
}
Why error?
Because [Link]() is defined as:
void run(); // no throws clause
2️⃣ Checked exceptions in Java (rule)
Java rule:
If a method throws a checked exception, it must be declared in the method
signature.
3️⃣ Functional interface controls exceptions
Runnable ❌
@FunctionalInterface
public interface Runnable {
void run(); // no throws
}
Lambda implementing it cannot throw checked exceptions.
Callable ✅
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Lambda implementing it CAN throw checked exceptions.
Callable<String> c = () -> {
if (true) throw new IOException();
return "ok";
};
4️⃣ Why Java designers did this (design reason)
❌ Allowing lambdas to throw arbitrary checked exceptions would break:
● Backward compatibility
● Interface contracts
● Type safety
Example:
Runnable r = () -> throw new IOException();
new Thread(r).start(); // Thread doesn't expect IOException
👉 Who will catch it? JVM? Thread? Caller?
This ambiguity is why it’s forbidden.
5️⃣ Why unchecked exceptions are allowed
Unchecked exceptions (RuntimeException) do not need declaration.
So this works:
Runnable r = () -> {
throw new RuntimeException("error");
};
6️⃣ Why anonymous classes look “more flexible” (but
aren’t)
Anonymous class:
Runnable r = new Runnable() {
public void run() {
try {
throw new IOException();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
Lambda:
Runnable r = () -> {
try {
throw new IOException();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
✔ Both behave the same
✔ Lambda is not more restrictive
7️⃣ Common workaround patterns
1️⃣ Use Callable instead of Runnable
Callable<Void> c = () -> {
throw new IOException();
};
2️⃣ Wrap checked exception
Runnable r = () -> {
try {
risky();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
3️⃣ Custom functional interface
@FunctionalInterface
interface ThrowingRunnable {
void run() throws IOException;
}
8️⃣ JVM / bytecode perspective
● Lambdas are compiled to methods
● Method signature must match functional interface
● JVM enforces exception compatibility
No special case for lambdas.
🔥 Interview-ready final answer
Lambdas cannot throw checked exceptions unless the functional interface method
declares them, because lambdas are implementations of interface methods and
must conform to their method signatures. Allowing arbitrary checked exceptions
would violate Java’s exception handling contract and break existing APIs.
One-liner memory trick 🧠
Lambda follows the interface — not the code inside it.
In Java 8, there are 4 main functional interfaces in the [Link] package that
are asked very often in interviews:
👉 Predicate, Consumer, Supplier, Function
I’ll explain each clearly with examples (easy to remember 👍).
1️⃣ Predicate<T>
✔️
Purpose:
✔️
Takes one input
✔️
Returns boolean (true/false)
Used for conditions / filtering
Method:
boolean test(T t);
Example:
Predicate<Integer> isEven = n -> n % 2 == 0;
[Link]([Link](10)); // true
[Link]([Link](7)); // false
Real use:
● filter() in Streams
● Checking conditions
2️⃣ Consumer<T>
✔️
Purpose:
✔️
Takes one input
✔️
Returns nothing (void)
Used to perform an action
Method:
void accept(T t);
Example:
Consumer<String> printName = name -> [Link](name);
[Link]("Dinesh");
Real use:
● forEach() in Streams
● Logging, printing, saving data
3️⃣ Supplier<T>
✔️
Purpose:
✔️
Takes no input
✔️
Returns a value
Used to supply or generate data
Method:
T get();
Example:
Supplier<Double> randomValue = () -> [Link]();
[Link]([Link]());
Real use:
● Lazy loading
● Generating IDs, default values
4️⃣ Function<T, R>
✔️
Purpose:
✔️
Takes one input
✔️
Returns one output
Used for data transformation
Method:
R apply(T t);
Example:
Function<String, Integer> lengthFunc = str -> [Link]();
[Link]([Link]("Java")); // 4
Real use:
● map() in Streams
● Converting one object to another
🧠 Easy Interview Trick (Remember This Table)
Interface Input Output Use Case
Predicate 1 boolea Condition check
n
Consume 1 void Perform action
r
Supplier 0 1 value Provide data
Function 1 1 value Transform data
🎯 Stream Example (All Together)
List<String> names = [Link]("Ram", "Dinesh", "Sai");
[Link]()
.filter(name -> [Link]() > 3) // Predicate
.map(String::toUpperCase) // Function
.forEach([Link]::println); // Consumer
In Java 8, apart from Predicate and Consumer, there are BiPredicate and BiConsumer
which work with two inputs.
1️⃣ BiPredicate<T, U>
✅ What it is
BiPredicate takes two arguments and returns a boolean.
@FunctionalInterface
public interface BiPredicate<T, U> {
boolean test(T t, U u);
}
✅ When to use
● When your condition depends on two values
● Example: comparing two numbers, validating username + password, checking key +
value
✅ Example
import [Link];
public class Demo {
public static void main(String[] args) {
BiPredicate<Integer, Integer> isSumEven =
(a, b) -> (a + b) % 2 == 0;
[Link]([Link](10, 20)); // true
[Link]([Link](10, 15)); // false
}
}
✅ Real-time example
BiPredicate<String, String> loginCheck =
(username, password) -> [Link]("admin") &&
[Link]("1234");
[Link]([Link]("admin", "1234")); // true
2️⃣ BiConsumer<T, U>
✅ What it is
BiConsumer takes two arguments and returns nothing.
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
✅ When to use
● When you want to perform an action using two values
● Example: printing key-value pairs, logging, storing data
✅ Example
import [Link];
public class Demo {
public static void main(String[] args) {
BiConsumer<String, Integer> printStudent =
(name, marks) -> [Link](name + " scored "
+ marks);
[Link]("Dinesh", 85);
}
}
✅ Real-time example (Map iteration)
Map<String, Integer> map = new HashMap<>();
[Link]("Java", 90);
[Link]("Spring", 85);
[Link]((key, value) ->
[Link](key + " => " + value));
[Link]() internally uses BiConsumer
🔑 Difference at a glance
Interface Input Return type Method
Predicate 1 boolean test()
BiPredicate 2 boolean test()
Consumer 1 void accept()
BiConsume 2 void accept()
r
In Java 8, UnaryOperator and BinaryOperator are special functional interfaces used when
input and output types are the same.
1️⃣ UnaryOperator<T>
✅ What it is
● Takes one input
● Returns same type as input
● It is a special case of Function<T, T>
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
T apply(T t);
}
✅ When to use
● Modify or transform a single value
● Example: increment value, convert string to uppercase
✅ Example
import [Link];
public class Demo {
public static void main(String[] args) {
UnaryOperator<Integer> square = x -> x * x;
[Link]([Link](5)); // 25
}
}
✅ Real-time example
UnaryOperator<String> toUpper = s -> [Link]();
[Link]([Link]("java")); // JAVA
✅ Static methods
UnaryOperator<Integer> same = [Link]();
[Link]([Link](10)); // 10
2️⃣ BinaryOperator<T>
✅ What it is
● Takes two inputs
● Returns same type as inputs
● It is a special case of BiFunction<T, T, T>
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
T apply(T t1, T t2);
}
✅ When to use
● Combine two values
● Example: addition, multiplication, max/min
✅ Example
import [Link];
public class Demo {
public static void main(String[] args) {
BinaryOperator<Integer> add = (a, b) -> a + b;
[Link]([Link](10, 20)); // 30
}
}
✅ Real-time example (Streams)
List<Integer> list = [Link](1, 2, 3, 4);
int sum = [Link]()
.reduce(0, Integer::sum);
[Link](sum); // 10
reduce() internally uses BinaryOperator
🔑 Key Differences
Interface Inputs Output Extends
Function<T,R> 1 R —
UnaryOperator<T> 1 T Function<T,T>
BiFunction<T,U,R> 2 R —
BinaryOperator<T>
Optional in Java 8
Optional is a container object used to represent a value that may or may not be present.
It helps avoid NullPointerException and makes code more readable.
Why Optional?
Before Java 8:
if(user != null) {
[Link]([Link]());
}
With Optional:
Optional<User> userOpt = [Link](user);
[Link](u -> [Link]([Link]()));
Creating Optional
1️⃣ [Link]() – value must NOT be null
Optional<String> opt = [Link]("Java"); // OK
// [Link](null); // ❌ NullPointerException
2️⃣ [Link]() – value may be null
Optional<String> opt = [Link](null); // OK
3️⃣ [Link]() – no value
Optional<String> opt = [Link]();
Important Methods
🔹 isPresent()
if([Link]()) {
[Link]([Link]());
}
🔹 ifPresent()
[Link](value -> [Link](value));
🔹 get() (⚠️ not recommended alone)
String val = [Link](); // throws exception if empty
Default Value Handling
🔹 orElse()
String name = [Link]("Default Name");
🔹 orElseGet() (lazy)
String name = [Link](() -> "Default Name");
🔹 orElseThrow()
String name = [Link](() ->
new RuntimeException("Value not found"));
Filtering & Mapping
🔹 filter()
Optional<String> opt = [Link]("Java");
[Link](s -> [Link]() > 3)
.ifPresent([Link]::println); // Java
🔹 map()
Optional<String> upper = [Link](String::toUpperCase);
🔹 flatMap()
Optional<Optional<String>> nested = [Link]([Link]("Java"));
Optional<String> flat = [Link](o -> o);
Real-time Example (Repository)
Optional<User> userOpt = [Link](1);
[Link](
user -> [Link]([Link]()),
() -> [Link]("User not found")
);
Best Practices (Interview Point ⭐)
✅ Use Optional as return type, not field
❌ Don’t use Optional for method parameters
❌ Don’t call get() without checking
✅ Prefer orElseGet() over orElse()
❌ Avoid Optional in entity fields (JPA issue)
Why Java 8 Date/Time API was introduced?
Problems with old Date & Calendar:
❌
❌
Mutable
❌
Not thread-safe
Confusing API (month starts from 0)
✅ Java 8 introduced [Link] which is:
● Immutable
● Thread-safe
● Clear & readable
Core Classes (Very Important ⭐)
1️⃣ LocalDate
👉 Date only (no time, no zone)
LocalDate today = [Link]();
LocalDate dob = [Link](1999, 5, 10);
[Link](today);
[Link](dob);
Common methods:
[Link](5);
[Link](1);
[Link]();
2️⃣ LocalTime
👉 Time only (no date, no zone)
LocalTime now = [Link]();
LocalTime time = [Link](10, 30, 45);
[Link](now);
3️⃣ LocalDateTime
👉 Date + Time (no timezone)
LocalDateTime dt = [Link]();
LocalDateTime custom = [Link](2026, 1, 10, 10, 30);
4️⃣ ZonedDateTime
👉 Date + Time + TimeZone
ZonedDateTime zdt = [Link]();
ZonedDateTime india = [Link]([Link]("Asia/Kolkata"));
Get all zones:
[Link]();
5️⃣ Instant
👉 Machine timestamp (UTC)
Instant instant = [Link]();
Used in:
● Logging
● Auditing
● Distributed systems
Formatting & Parsing (Most Asked ⭐)
DateTimeFormatter
LocalDate date = [Link]();
DateTimeFormatter formatter =
[Link]("dd-MM-yyyy");
String formatted = [Link](formatter);
[Link](formatted);
Parsing
String str = "10-01-2026";
LocalDate parsed =
[Link](str, formatter);
Period & Duration (Difference is IMPORTANT ⭐)
🔹 Period – Date based
Period p = [Link](
[Link](2000,1,1),
[Link]());
[Link]([Link]());
🔹 Duration – Time based
Duration d = [Link](
[Link](10,0),
[Link](12,30));
[Link]([Link]()); // 150
Type Used for
Period Years, Months, Days
Duration Hours, Minutes,
Seconds
Comparison (Very Common)
LocalDate d1 = [Link]();
LocalDate d2 = [Link](2025, 1, 1);
[Link](d2);
[Link](d2);
[Link](d2);
Convert Old Date to Java 8 Date
Date date = new Date();
LocalDate localDate = [Link]()
.atZone([Link]())
.toLocalDate();
Interview One-Liners ⭐
● LocalDate → Date without time
● LocalTime → Time without date
● LocalDateTime → Date + time without zone
● ZonedDateTime → Date + time + zone
● Instant → Timestamp in UTC
● DateTimeFormatter → Formatting & parsing
● Period vs Duration → Date vs Time difference
Real Project Example
LocalDateTime createdAt = [Link]();
LocalDateTime expiry = [Link](30);
Top Interview Questions (Practice)
1. Difference between LocalDate and LocalDateTime?
2. Period vs Duration?
3. Why Java 8 Date API is thread-safe?
4. How to format date in Java 8?
5. How to handle timezones?
✅
If you want, I can give you:
1-page PDF notes
✅ Interview Q&A
✅ Spring Boot + DateTime usage
✅ Coding questions
Just tell me 💪
Top Interview Questions (Practice) Difference between LocalDate and LocalDateTime? Period
vs Duration? Why Java 8 Date API is thread-safe? How to format date in Java 8? How to handle
timezones? give me the answer for this
👌
Here are clear, interview-ready answers for each question. You can memorize these and
explain confidently
1️⃣ Difference between LocalDate and LocalDateTime
Answer:
● LocalDate represents only date (year, month, day)
● LocalDateTime represents date + time (year, month, day, hour, minute, second)
● Neither contains timezone
Example:
LocalDate date = [Link](); // 2026-01-10
LocalDateTime dateTime = [Link](); // 2026-01-10T21:30:45
Interview one-liner:
LocalDate is used when time is not required, while LocalDateTime is used
when both date and time are needed.
2️⃣ Difference between Period and Duration
Answer:
● Period is date-based (years, months, days)
● Duration is time-based (hours, minutes, seconds)
● Period works with LocalDate
● Duration works with LocalTime, Instant
Example:
Period period = [Link](
[Link](2000,1,1),
[Link]());
Duration duration = [Link](
[Link](10,0),
[Link](12,30));
Interview one-liner:
Period measures date differences, while Duration measures time differences.
3️⃣ Why Java 8 Date API is Thread-Safe?
Answer:
● All Java 8 date/time classes are immutable
● Once created, the object cannot be modified
● Any change creates a new object
● No shared mutable state → thread-safe by design
Example:
LocalDate d1 = [Link]();
LocalDate d2 = [Link](1); // d1 is unchanged
Interview one-liner:
Java 8 Date API is thread-safe because its classes are immutable.
4️⃣ How to Format Date in Java 8?
Answer:
Using DateTimeFormatter
Example:
LocalDate date = [Link]();
DateTimeFormatter formatter =
[Link]("dd-MM-yyyy");
String formattedDate = [Link](formatter);
Parsing example:
LocalDate parsedDate =
[Link]("10-01-2026", formatter);
Interview one-liner:
DateTimeFormatter is used for formatting and parsing dates in Java 8.
5️⃣ How to Handle Timezones in Java 8?
Answer:
Using ZonedDateTime and ZoneId
Example:
ZonedDateTime indiaTime =
[Link]([Link]("Asia/Kolkata"));
ZonedDateTime usaTime =
[Link]([Link]("America/New_York"));
Convert between zones:
ZonedDateTime converted =
[Link](
[Link]("America/New_York"));
Interview one-liner:
Java 8 handles timezones using ZonedDateTime and ZoneId.
⭐ Final 30-Second Revision Cheat Lines
● LocalDate → Date only
● LocalDateTime → Date + Time
● Period → Date difference
● Duration → Time difference
● Thread-safe → Immutable classes
● Formatting → DateTimeFormatter
● Timezone → ZonedDateTime + ZoneId
Day 2 – Java Memory & JVM
These notes are perfect for interviews + real-time understanding.
📘 Day 2 – Java Memory & JVM
1️⃣ JVM Architecture
👉 What is JVM?
JVM (Java Virtual Machine) is responsible for:
● Running Java bytecode
● Managing memory
● Garbage collection
● Providing platform independence
📌 Java code → Bytecode → JVM → OS
🧩 JVM Components
JVM
├── Class Loader
├── Runtime Data Areas
│ ├── Method Area
│ ├── Heap
│ ├── Stack
│ ├── PC Register
│ └── Native Method Stack
└── Execution Engine
├── Interpreter
├── JIT Compiler
└── Garbage Collector
🔹 1. Class Loader Subsystem
Loads .class files into memory.
Types:
1. Bootstrap ClassLoader
○ Loads core classes ([Link].*)
2. Extension ClassLoader
○ Loads classes from ext directory
3. Application ClassLoader
○ Loads application classes
📌 Uses Parent Delegation Model
🔹 2. Runtime Data Areas
(a) Method Area (Metaspace)
Stores:
● Class metadata
● Method definitions
● Static variables
● Constant pool
📌 Since Java 8 → Metaspace (Native Memory)
(b) Heap Memory
Stores:
● Objects
● Instance variables
● ✔ Shared across threads
✔ Managed by Garbage Collector
(c) Stack Memory
Stores:
● Method calls
● Local variables
● Reference variables
✔ Thread-specific
❌
✔ Fast access
Smaller size
(d) PC Register
● Stores current executing instruction address
● One per thread
(e) Native Method Stack
● Used for native methods (C/C++)
🔹 3. Execution Engine
1. Interpreter
○ Executes bytecode line by line
○ Slow
2. JIT Compiler
○ Converts bytecode to native code
○ Improves performance
3. Garbage Collector
○ Frees unused memory
2️⃣ Heap vs Stack
🔁 Comparison Table
Feature Heap Stack
Stores Objects Method calls & local vars
Memory Size Large Small
Thread Shared Thread-specific
Safety
Speed Slower Faster
GC Involved Yes No
Lifetime Long Short
🔍 Example
void test() {
int x = 10; // Stack
Student s = new Student(); // s → Stack, object → Heap
}
🧠 Key Interview Points
● Reference → Stack
● Object → Heap
● Stack overflow = deep recursion
● Heap overflow = too many objects
3️⃣ Garbage Collection (GC)
👉 What is Garbage Collection?
Automatic process of removing unused objects from heap memory.
✔ Improves memory management
✔ Prevents memory leaks (but not fully)
🔍 How GC Works?
An object becomes eligible for GC when:
● No active references exist
Student s = new Student();
s = null; // eligible for GC
🧩 Heap Structure (Generational GC)
Heap
├── Young Generation
│ ├── Eden
│ ├── Survivor S0
│ └── Survivor S1
└── Old Generation
🔄 GC Types
GC Type Description
Minor GC Cleans Young
Generation
Major GC Cleans Old Generation
Full GC Cleans entire heap
🧠 Common GC Algorithms
● Serial GC
● Parallel GC
● CMS (Concurrent Mark Sweep)
● G1 GC (default in modern Java)
● ZGC / Shenandoah (low latency)
❌ Explicit GC Call
[Link](); // Not guaranteed
4️⃣ Memory Leaks in Java
👉 What is a Memory Leak?
Memory leak occurs when:
Objects are no longer needed but still referenced, so GC cannot clean them.
🔥 Common Causes
1. Static References
static List list = new ArrayList();
Objects live till JVM shutdown.
2. Unclosed Resources
● DB connections
● File streams
● Sockets
❌ Not closing them causes memory leak
3. Listener References
● Event listeners not removed
4. Caching Without Limit
Map cache = new HashMap(); // no eviction
5. Inner Class Reference
Non-static inner classes implicitly hold a reference to their enclosing outer class. If the inner
class object lives longer than the outer class, this reference prevents garbage collection of the
outer object, causing a memory leak. This can be avoided by making the inner class static,
using top-level classes, or weak references
🔍 How to Detect Memory Leaks
● Heap dump analysis
● Tools:
○ JVisualVM
○ JConsole
○ Eclipse MAT
○ JProfiler
🛠 How to Prevent Memory Leaks
✔ Use try-with-resources
✔ Remove unused listeners
✔ Use WeakHashMap
✔ Avoid unnecessary static references
✔ Set cache eviction policies
🎯 Interview One-Liners
● “Heap stores objects, stack stores method execution.”
● “GC works only on heap memory.”
● “Memory leak happens when objects are referenced but unused.”
● “Stack memory is thread-safe; heap is shared.”
● “Metaspace replaced PermGen from Java 8