Interview Question Java Developer Fintech
📌 Interview Questions for Shivam Kumar
🔹 Core Java & OOPs
-
What is the difference between
HashMap
andConcurrentHashMap
? -
How does
volatile
work in Java memory model? When do you use it? -
Explain the difference between
synchronized
methods vs.ReentrantLock
. -
How does Garbage Collection work in Java? Can you force GC?
-
What is the difference between
final
,finally
, andfinalize()
? -
How do you implement immutability in a Java class?
🔹 Multithreading & Concurrency
-
What is the difference between
Runnable
andCallable
? -
What is the difference between
CompletableFuture.runAsync()
andsupplyAsync()
? -
How do thread pools work? What is the advantage of
ExecutorService
? -
What are daemon threads in Java?
-
How does
ForkJoinPool
differ fromThreadPoolExecutor
?
🔹 Spring Boot & Microservices
-
What is the difference between
@Component
,@Service
,@Repository
, and@Controller
? -
How does
@Transactional
work internally? -
What are the different scopes of Spring beans?
-
How do you implement exception handling in REST APIs?
-
Explain the difference between Monolithic vs. Microservices architecture.
-
What are Circuit Breakers? How do you implement them in Spring Boot?
-
How does Spring Boot auto-configuration work?
🔹 Kafka & Event-Driven Systems
-
Explain the difference between Kafka Producer, Consumer, Broker, and Zookeeper.
-
How does Kafka ensure exactly-once delivery?
-
What is the difference between Kafka topic partitions and replication factor?
-
How do you handle retries & dead-letter topics in Kafka?
-
What are the use cases of Kafka in payment systems?
🔹 Databases (SQL + NoSQL)
-
What is an Index? How does database indexing work internally?
-
Difference between Clustered and Non-Clustered Index.
-
Write an SQL query to find the nth highest salary.
-
How do you optimize a slow SQL query?
-
What is the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN?
-
Transaction isolation levels: READ COMMITTED, REPEATABLE READ, SERIALIZABLE.
-
Redis use case in caching transactions.
🔹 Fintech/Payments Domain (for NPCI experience)
-
How do you ensure idempotency in payment APIs?
-
How did you handle encryption (AES/RSA) in your project?
-
What is the difference between real-time processing vs. batch settlement?
-
How do you design a reconciliation system for cross-border payments?
-
Explain how you would implement a transaction alert system using Kafka.
DSA & Problem-Solving
-
Reverse a linked list (iterative & recursive).
-
Detect a cycle in a linked list.
-
Find the K largest elements in an array (Heap approach).
-
Check if a string is a valid palindrome (ignore special characters).
-
Find the Nth prime number.
-
Merge two sorted linked lists.
-
Implement LRU Cache.
🔹 Behavioral / Project Questions
-
Can you explain a challenging bug you faced in NPCI’s UPI/RuPay system and how you solved it?
-
How do you ensure security in financial transactions?
-
What was the biggest performance bottleneck in your project and how did you fix it?
-
How do you handle API versioning in Microservices?
-
If a payment service goes down, how do you ensure fault tolerance?
Question 1
👉 “Can you explain the difference between synchronized
, volatile
, and ConcurrentHashMap
in Java, and when would you use each in a multi-threaded application?”
synchronized
: Ensures that only one thread can execute a method/block at a time (mutual exclusion) and provides visibility guarantees. It can cause contention and, if used incorrectly, deadlocks.-
volatile
: Ensures visibility of updates to a variable across threads, but does not provide atomicity. Useful for flags and status checks. -
ConcurrentHashMap
: A thread-safe alternative to HashMap that avoids locking the entire map (unlike Hashtable). It uses fine-grained locks and CAS operations so multiple threads can read/write concurrently.
Java Question 2
👉 “Suppose you need to implement a thread-safe counter in Java. Show me three different ways you can do it: using synchronized
, using volatile
, and using AtomicInteger
. Also explain which approach is best and why.”
1- Synchronized used -
public class SynchronisedThread extends Thread {
private int count = 0;
// synchronized method to avoid race condition
synchronized void counter() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronisedThread obj = new SynchronisedThread();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.counter();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.counter();
}
});
t1.start();
t2.start();
// wait for both threads to finish
t1.join();
t2.join();
System.out.println("Counter: " + obj.getCount()); // should be 2000
}
}
🔹 Key Fixes
-
Added
synchronized
tocounter()
→ prevents race condition. -
Used
t1.join()
andt2.join()
→ ensures main thread waits until both threads finish.
🔹 Why use AtomicInteger
?
-
When multiple threads modify a shared variable, we face a race condition.
-
Normally, we fix this by using
synchronized
. Butsynchronized
is blocking (only one thread enters at a time → slower). -
Java provides the
java.util.concurrent.atomic
package, which has atomic classes (likeAtomicInteger
,AtomicLong
, etc.) that handle such cases more efficiently using low-level CPU instructions (CAS – Compare-And-Swap). -
So, no explicit
synchronized
keyword is needed.
🔹 Why AtomicInteger
is better than synchronized
(in some cases)
1. Performance
-
synchronized
→ uses locks → only one thread enters the critical section at a time. -
AtomicInteger
→ lock-free → uses low-level CAS (Compare-And-Swap) from the CPU.
👉 This makesAtomicInteger
faster in multi-threaded counters.
2. Non-blocking
-
With
synchronized
, if one thread holds the lock, others must wait (blocked). -
With
AtomicInteger
, threads do not block; they retry CAS until success.
👉 Better throughput under high concurrency.
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicThreadExample {
private AtomicInteger count = new AtomicInteger(0);
void counter() {
count.incrementAndGet(); // atomic increment
}
public int getCount() {
return count.get();
}
public static void main(String[] args) throws InterruptedException {
AtomicThreadExample obj = new AtomicThreadExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.counter();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.counter();
}
});
t1.start();
t2.start();
// wait for both threads to finish
t1.join();
t2.join();
System.out.println("Counter: " + obj.getCount()); // Always 2000
}
}
🔹 What is volatile
?
-
volatile
is a keyword in Java used for shared variables between multiple threads. -
When a variable is declared
volatile
:-
Its value is always read from main memory, not from thread’s local cache.
-
Any write to it is immediately visible to all other threads.
-
👉 It ensures visibility, but not atomicity.
🔹 Why do we need it?
Without volatile
, each thread might cache the variable locally → changes by one thread may not be visible to another.
🔹 Limitations of volatile
-
Only guarantees visibility, not atomicity.
🔹 Step 1: What happens in count++
Suppose we have:
This looks like a single operation, but under the hood it is actually 3 steps:
-
Read → fetch value of
count
from memory into CPU/register. -
Modify → add 1 to the value in the register.
-
Write → store the new value back into memory.
❌ Problem: Race condition
If two threads run count++
at the same time:
-
Initial
count = 0
-
Thread 1 reads
0
, increments to1
-
Thread 2 also reads
0
, increments to1
-
Both write
1
back → lost update
👉 So instead of 2
, final value is 1
.
This is why count++
is not atomic.
🔹 Step 2: How AtomicInteger
fixes it
AtomicInteger
uses Compare-And-Swap (CAS) at the CPU level.
CAS mechanism:
-
Read the current value (
expected
). -
Try to set a new value.
-
If the value in memory is still the same as
expected
, update succeeds. -
If another thread already changed it, retry until success.
👉 This ensures atomicity without locks.
🔹 Summary
-
count++
= not atomic (read-modify-write, race condition possible). -
AtomicInteger.incrementAndGet()
= atomic CAS (retry until success, thread-safe, no locks).
Java Question 3 (Volatile)
👉 “What does the volatile
keyword do in Java? Can you use it to make a counter thread-safe? Why or why not? Show me with code.”
🔹 What is volatile
?
-
volatile
is a keyword in Java used for variables that may be modified by multiple threads. -
It ensures:
-
Visibility → Changes made by one thread are immediately visible to other threads.
-
No caching in thread’s local memory (CPU cache) → Always read/write directly from main memory.
-
⚠️ But note:
-
volatile
does NOT guarantee atomicity (operations likecount++
are not thread-safe with justvolatile
). -
It only ensures latest value visibility.
✅ Summary:
-
Use
volatile
for flags, status variables, stop signals. -
Do NOT use
volatile
for counters or complex shared state → usesynchronized
orjava.util.concurrent.atomic
classes.
Java Question 4
👉 “Explain how wait()
and notify()
work in Java. Can you write a small producer–consumer example using them?”
The wait() method is used for interthread communication.
The wait() method is tightly integrated with the synchronization lock
The wait() method is defined in the Object class
When wait() is called on a thread holding the monitor lock, it surrenders the monitor lock and enters the waiting state.
The notify() method is used to wake up a single thread
The notify() method is used to give the notification for one thread for a particular object.
The notify() method does not have any return type value
When the notify() is called on a thread holding the monitor lock, it symbolizes that the thread is soon going to surrender the lock.
wait() and notify() are methods of Object class in Java, used for inter-thread communication.
When one thread is working inside a synchronized block and cannot proceed (e.g., producer waiting for buffer space), it calls wait().
This releases the lock and puts the thread in WAITING state until another thread calls notify() or notifyAll().
notify() is called by another thread (e.g., consumer after consuming an item) to wake up one waiting thread.
Producer puts items in a buffer. If buffer is full → wait().
Consumer takes items from buffer. If buffer is empty → wait ().
Each side uses notify () to signal the other.
⚠️ Key point: wait () must be called inside a synchronized block because it needs the monitor lock.
With that in mind, we’ll make use of the following:
- Object.wait() to suspend a thread
- Object.notify() to wake a thread up
public class WaitNotifyExample {
private int data;
boolean available=false;
public synchronized void produce(int value) throws InterruptedException
{
while(available){
wait();//wait until cosumer consumer
}
data =value;
System.out.println("produce "+value);
available=true;
notify();
}
public synchronized int consume() throws InterruptedException
{
while (!available)
{
wait(); // wait until producer produces
}
System.out.println("consume "+data);
available=false;
notify();
return data;
}
public static void main(String[] args) {
WaitNotifyExample obj=new WaitNotifyExample();
Thread producer = new Thread(()->{
try
{
for(int i=1;i<=10;i++)
{
obj.produce(i);
Thread.sleep(500);
}
}
catch(InterruptedException e)
{
e.printStackTrace();
}
});
Thread consumer=new Thread(()->{
try{
for (int i=1;i<=10;i++)
{
obj.consume();
Thread.sleep(500);
}
}catch(Exception e)
{
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
🔑 Key Points (for interviews)
-
wait()
→ releases lock and suspends thread. -
notify()
→ wakes up one waiting thread (usenotifyAll()
if multiple). -
Always used inside
synchronized
to avoid race conditions.
🔹 Concept: BlockingQueue
-
A
BlockingQueue
is a thread-safe queue provided byjava.util.concurrent
. -
It is designed for Producer–Consumer problems so you don’t have to manually manage
wait()
andnotify()
. -
Two main methods:
-
put()
→ producer inserts an item. If the queue is full, producer waits automatically. -
take()
→ consumer removes an item. If the queue is empty, consumer waits automatically.
-
-
This removes the need for explicit
synchronized
,wait()
, andnotify()
. -
Common implementations:
-
ArrayBlockingQueue
(fixed size) -
LinkedBlockingQueue
(optional capacity)
-
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue= new ArrayBlockingQueue<>(3);
Thread producer = new Thread(() ->{
try
{
for(int i=1;i<=5;i++)
{
queue.put(i);
System.out.println("Produce "+i);
Thread.sleep(100);
}
}catch(InterruptedException e)
{
e.printStackTrace();
}
});
Thread consumer = new Thread(() ->{
try
{
for(int i=1;i<=5;i++)
{
int value=queue.take();
System.out.println("cosume "+value);
Thread.sleep(100);
}
}catch(InterruptedException e)
{
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
🔑 Interview Takeaways
-
With
BlockingQueue
, you don’t manually callwait()
ornotify()
. -
It’s safer, cleaner, and less error-prone.
-
In real-world microservices or fintech systems,
BlockingQueue
is preferred over rawwait/notify
.
volatile
, synchronized
, and final
play in visibility and ordering of variables?”The Java Memory Model (JMM) defines how threads interact with memory and what guarantees Java provides for visibility, ordering, and atomicity of shared variables.
-
Without JMM, each thread may cache variables locally → leading to stale or inconsistent values.
-
JMM ensures correct interaction through rules like happens-before relationships.
Key constructs in JMM:
-
volatile
→ guarantees visibility and prevents instruction reordering, but not atomicity. -
synchronized
→ ensures both mutual exclusion and visibility (via happens-before). -
Atomic classes
(AtomicInteger
, etc.) → use low-level Compare-And-Swap (CAS) to achieve atomicity without locks. -
final
fields → once an object is constructed and published safely, its final fields are visible correctly to other threads (safe publication).
👉 In short:
JMM solves issues of visibility, ordering, and atomicity, making multithreaded programming in Java safe and predictable.
❓ Interview Question 6:
"What is the difference between Callable
and Runnable
in Java? Can you show a practical example where Callable
is preferred over Runnable
?
✅ Runnable
-
Introduced in Java 1.0.
-
Has a single method
run()
. -
Cannot return a result.
-
Cannot throw checked exceptions.
-
Suitable when you just need to execute a task in a separate thread without expecting any output.
✅ Callable
-
Introduced in Java 5 (as part of
java.util.concurrent
). -
Has a single method
call()
. -
Can return a result (
V
generic type). -
Can throw checked exceptions.
-
Usually executed via an
ExecutorService
usingsubmit()
. -
Works with
Future
to get result, cancel tasks, or check status.
💡 When to use Callable over Runnable:
-
When you need the thread to return a result.
-
When you want to propagate checked exceptions from the task.
-
For long-running tasks where you want to cancel/monitor them using
Future
.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableVsRunnableExample {
public static void main(String[] args) throws Exception {
// Create thread pool
ExecutorService executorService = Executors.newFixedThreadPool(3);
// Runnable task (does not return result)
Runnable runnableTask = () -> {
System.out.println("Runnable task executed by: " + Thread.currentThread().getName());
};
// Callable task (returns result)
Callable<String> callableTask = () -> {
Thread.sleep(500); // simulate some work
return "Callable task result from: " + Thread.currentThread().getName();
};
// Execute Runnable (no return value)
executorService.execute(runnableTask);
// Submit Callable (returns Future)
Future<String> future = executorService.submit(callableTask);
// Get result from Callable
String result = future.get();
System.out.println("Callable returned: " + result);
// Shutdown executor
executorService.shutdown();
}
}
sleep()
and wait()
✅ Summary in your words:
-
sleep()
→ Thread pauses but still blocks others from using the lock. -
wait()
→ Thread pauses and frees the lock, so other threads can run.
notify()
and notifyAll()
in Java?Quick Interview Summary
-
notify()
→ wakes one random thread from the wait set. -
notifyAll()
→ wakes all waiting threads, but only one at a time will acquire the lock and continue.
synchronized
block and ReentrantLock
in Java?Synchronized block
-
Easy to use (
synchronized
keyword). -
A thread waits indefinitely until the lock is available.
-
Can cause thread starvation (if one thread keeps holding lock).
-
No flexibility (no tryLock, no timed lock, no interruptible lock).
ReentrantLock (from java.util.concurrent.locks
)
-
Provides the same mutual exclusion, but with more advanced features:
-
lock()
→ acquire lock (like synchronized). -
unlock()
→ release lock (must always call infinally
). -
tryLock()
→ try to get lock; if not available, thread can go do other work instead of waiting. -
tryLock(timeout)
→ wait for some time, then give up if not available. -
lockInterruptibly()
→ thread can be interrupted while waiting.
-
-
Avoids starvation and gives better control.
👉 See the difference:
-
With
synchronized
, Thread-2 must wait until Thread-1 releases. -
With
ReentrantLock.tryLock()
, Thread-2 does not wait and goes to do something else.
submit()
and execute()
in ExecutorService?execute()
→-
Defined in Executor interface.
-
Takes only a
Runnable
. -
Does not return anything (fire-and-forget).
-
If the task throws an exception, it goes to the thread’s uncaught exception handler.
-
-
submit()
→-
Defined in ExecutorService.
-
Can take
Runnable
orCallable
. -
Always returns a
Future
object, which can be used to get result or handle exceptions. -
More flexible because you can check task status (
isDone()
,cancel()
, etc.).
-
👉 In short:
-
Use
execute()
when you don’t care about result. -
Use
submit()
when you need result or exception handling.
Future
-
It is just an interface (object reference) that represents the result of an asynchronous computation.
-
You get it when you call
submit()
onExecutorService
. -
With it, you can:
-
get()
the result (blocking). -
cancel()
the task. -
check
isDone()
,isCancelled()
.
-
-
But
Future
itself does not run anything — it only holds task’s result.
-
-
FutureTask
-
It is a concrete class that implements both
Runnable
andFuture
. -
You can wrap a
Callable
(or Runnable) into aFutureTask
. -
Then you can either:
-
submit it to an Executor, or
-
run it directly in a Thread.
-
-
Useful when you want both: a task to run and a Future handle to manage its result.
-
👉 In short:
-
Future
→ a handle to the result. -
FutureTask
→ a task implementation that can be executed and gives you aFuture
.
💯 Correct — you got the main drawback of Future
right:
-
With
Future.get()
, the main thread blocks until the result is ready. -
This can hurt performance and responsiveness.
👉 That’s why Java introduced CompletableFuture
(Java 8).
🔹 Interview-Ready Explanation
Drawbacks of Future:
-
get()
blocks → can’t do other work in the meantime. -
No simple way to chain multiple async tasks.
-
No built-in callback mechanism (you have to poll
isDone()
).
How CompletableFuture solves it:
-
Non-blocking (async).
-
Supports callbacks (
thenApply
,thenAccept
,thenRun
). -
Supports chaining of multiple tasks.
-
Supports combining multiple futures (
thenCombine
,allOf
, etc.). -
Provides methods like
supplyAsync()
,runAsync()
to run tasks in the background.
✅ Summary you can tell in interview:
-
Future
→ blocking, no chaining, limited. -
CompletableFuture
→ non-blocking, supports chaining & callbacks, better for async programming.
🔹 What is a Callback?
👉 A callback is a function/method that you pass to another method, which will be executed later when a task completes.
-
Instead of blocking and waiting for the result, you say:
“Hey, when you’re done, call this method for me.”
That "method to call later" = callback.
🔹 Example in Simple Terms
Imagine you order pizza 🍕:
-
You don’t sit in the shop until it’s ready (blocking).
-
You give them your phone number 📞.
-
When pizza is ready, they call you back.
That phone number = callback.
✅ Interview Definition:
A callback is a function that is invoked automatically after a task completes, instead of waiting synchronously. It improves responsiveness and enables async programming.
What is the difference between CompletableFuture.runAsync() and CompletableFuture.supplyAsync()?”
Interview Style Answer
-
runAsync()
is used when the task does not return any result. -
supplyAsync()
is used when the task returns a result. -
Both are non-blocking and run in the ForkJoinPool.commonPool() by default (unless you provide your own
Executor
).
ExecutorService
and ForkJoinPool
in Java? When would you use one over the other?"🔹 What is ExecutorService?
-
In Java, if we create threads using
new Thread()
, we manually manage lifecycle (start, stop, join). -
For large applications, this becomes hard to manage.
-
ExecutorService
(fromjava.util.concurrent
) solves this by providing a thread pool where threads are reused.
So instead of creating a new thread every time, you just submit a task (Runnable
or Callable
) and ExecutorService will handle scheduling, execution, and lifecycle.
🔹 Key Methods of ExecutorService
-
execute(Runnable task)
→ runs a Runnable task (no return). -
submit(Callable<T> task)
→ runs a Callable task and returns aFuture<T>
. -
shutdown()
→ stops accepting new tasks but lets existing tasks finish. -
shutdownNow()
→ forcefully stops tasks. -
invokeAll(Collection<Callable>)
→ runs multiple tasks and waits for all results. -
invokeAny(Collection<Callable>)
→ runs multiple tasks and returns the result of the first successful one.
🔹 ExecutorService
-
Part of
java.util.concurrent
. -
Provides a thread pool to manage tasks (instead of manually creating threads).
-
You submit tasks (
Runnable
orCallable
) → it schedules them in a fixed number of threads. -
Good for independent tasks like handling requests, sending emails, DB queries, etc.
-
Example:
FixedThreadPool
,CachedThreadPool
,ScheduledThreadPool
.
👉 Use when tasks are independent and don’t rely on splitting into subtasks.
🔹 ForkJoinPool
-
Part of
java.util.concurrent
(since Java 7). -
Uses work-stealing algorithm → idle threads can "steal" tasks from busy ones.
-
Best for divide-and-conquer / recursive algorithms (parallelism).
-
Example: Parallel streams in Java 8 use ForkJoinPool internally.
👉 Use when tasks can be broken into smaller subtasks (e.g., parallel sort, Fibonacci, matrix multiplication).
🔹 1. What are Java Streams?
-
Introduced in Java 8 inside
java.util.stream
. -
Stream = a sequence of elements that you can process (filter, map, reduce, collect, etc.) in a functional style.
-
Example:
public static void main(String[] args) {
List<String> names= Arrays.asList("Annkit","Aadff","Swati","Sneffdf");
List<String> result= names.stream().filter(name ->name.startsWith("A")).collect(Collectors.toList());
System.out.println(result);
}
}
.
🔹 2. Parallel Streams
-
parallelStream()
splits data into chunks and processes them in multiple threads using ForkJoinPool (common pool).
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Sum: " + sum);
🔹 3. When Parallel Stream is Harmful?
Parallel streams are not always faster. In fact, they can slow you down if:
-
Small Data Set → overhead of splitting/joining > benefit.
-
Shared Mutable State → causes race conditions.
stream()
vs parallelStream()
?In Java 8, the Stream API is used to process collections of data in a functional and declarative style.
-
Operations like
filter()
,map()
,reduce()
,collect()
let us transform and aggregate data in a clean way. -
By default, a stream processes sequentially (one element at a time, in order).
-
-
A parallel stream splits the data source into multiple chunks and processes them concurrently using the ForkJoinPool framework.
-
Each chunk runs in a separate thread.
-
Finally, results are combined (merged) to produce the output.
-
-
⚠️ Trade-offs:
-
Good for large CPU-bound tasks.
-
Bad for small datasets, shared mutable state, or I/O-bound tasks because overhead can hurt performance or cause race conditions.
@Component
, @Service
, @Repository
, and @Controller
in Spring? When would you use each?✅ Spring Stereotype Annotations
-
@Component
-
Generic stereotype for any Spring-managed bean.
-
Tells Spring: “Create an instance of this class and manage it in the ApplicationContext.”
-
Can be used for any class that doesn’t fall under a more specific stereotype.
-
-
@Service
-
Specialization of
@Component
. -
Indicates the class contains business logic / service layer code.
-
Mainly used for clarity and readability — Spring treats it the same as
@Component
.
-
-
@Repository
-
Specialization of
@Component
for DAO / persistence layer. -
Adds exception translation: Spring automatically converts database exceptions (like
SQLException
) into Spring’sDataAccessException
.
-
-
@Controller
-
Specialization of
@Component
for presentation layer. -
Handles HTTP requests and returns a view (HTML, JSP, Thymeleaf) or response data.
-
💡 Tip for interviews:
-
Use
@Component
for generic beans. -
Use
@Service
for business logic. -
Use
@Repository
for database access. -
Use
@Controller
for web layer / request handling.
✅ @Controller
vs @RestController
-
@Controller
-
Handles HTTP requests.
-
Typically used to return a view (HTML, JSP, Thymeleaf).
-
If you want to return JSON or XML, you need to annotate the method with
@ResponseBody
.
-
-
@RestController
-
Specialization of
@Controller
+@ResponseBody
. -
All methods automatically serialize the returned object to JSON or XML.
-
Used for building RESTful APIs.
public class MyController {
@GetMapping("/hello")
public String hello() {
return "hello.html"; // returns view
}
}
@RestController
public class MyRestController {
@GetMapping("/api/hello")
public String helloApi() {
return "Hello JSON"; // returns response body directly
}
}
@RequestParam
, @PathVariable
, and @RequestBody
in Spring Boot? When would you use each?"✅ Spring Boot: @PathVariable
, @RequestParam
, @RequestBody
-
@PathVariable
-
Used to extract values from the URI path.
-
Example:
@GetMapping("/users/{id}")
public User getUserById(@PathVariable int id) {
return userService.getById(id);
}
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name, @RequestParam int age) {
return userService.filterByNameAndAge(name, age);
}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
Request body {
"name":shiv Ji
💡 Interview tip:
-
PathVariable → identity / specific resource.
-
RequestParam → filtering / optional query parameters.
-
RequestBody → full object / payload in POST/PUT requests.
@ExceptionHandler
and @ControllerAdvice
."public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable int id) {
if (id <= 0) throw new IllegalArgumentException("Invalid ID");
return new User(id, "Shivam");
}
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument(IllegalArgumentException ex) {
return ex.getMessage();
}
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body(ex.getMessage());
}
}
🔹 Pagination & Sorting in Spring Boot
Spring Data JPA provides built-in support for pagination and sorting using the Pageable
and Sort
interfaces.
✅ Interview Tips
-
Always mention why pagination is important → avoids loading huge datasets in memory, improves performance.
-
Sorting is straightforward via
Sort
object. -
Spring Data JPA makes this very easy with
Pageable
andSort
.
1️⃣ Pagination
-
Pageable lets you specify:
-
page
→ page number (0-based index) -
size
→ number of items per page
-
-
Example repository method:
Page<User> findAll(Pageable pageable);
}
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping
public Page<User> getUsers(@RequestParam int page, @RequestParam int size) {
return userRepository.findAll(PageRequest.of(page, size));
}
}
public List<User> getUsersSorted() {
return userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));
}
public Page<User> getPagedAndSortedUsers(@RequestParam int page,
@RequestParam int size,
@RequestParam String sortBy) {
return userRepository.findAll(PageRequest.of(page, size, Sort.by(sortBy)));
}
@Cacheable
, @CachePut
, and @CacheEvict
.✅ Spring Boot Caching with Redis
-
Enable caching
-
Add
@EnableCaching
in your main Spring Boot application class. -
This tells Spring to look for caching annotations on methods.
@EnableCaching
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Connect Redis
-
Add Redis dependency in
pom.xml
and configureapplication.properties
:
spring.redis.port=6379
Caching Annotations
-
@Cacheable
-
Used on a method to store the result in cache.
-
Next time the method is called with the same parameters, the cached value is returned (no DB call).
public User getUserById(Long id) {
System.out.println("Fetching from DB...");
return userRepository.findById(id).orElse(null);
}
@CachePut
-
Updates cache without skipping method execution.
-
Useful when you want to update both DB and cache.
public User updateUser(User user) {
return userRepository.save(user);
}
@CacheEvict
-
Removes entry from cache.
-
Useful when deleting or updating data.
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
🔑 Interview Tips
-
Always mention why caching is important → reduces DB calls, improves performance.
-
Know Redis as a key-value store, Spring Boot integrates easily via
spring-boot-starter-data-redis
. -
@Cacheable
→ read caching,
@CachePut
→ update caching,
@CacheEvict
→ delete from cache.
🔹 1. Event-Driven Architecture (EDA) Concept
-
In an event-driven system, services communicate via events instead of direct API calls.
-
Producer → publishes an event (message) when something happens.
-
Consumer → subscribes to events and reacts to them asynchronously.
-
Advantages:
-
Loose coupling between services.
-
Asynchronous processing → improves scalability.
-
Can integrate multiple microservices without tight dependencies.
-
Example:
-
Payment service publishes
PaymentSuccess
event. -
Notification service consumes it to send an email.
🔹 2. Kafka Overview
-
Apache Kafka is a distributed publish-subscribe messaging system.
-
Concepts:
-
Topic → category of messages.
-
Producer → writes messages to topic.
-
Consumer → reads messages from topic.
-
Broker → Kafka server node.
-
-
Kafka is high-throughput, fault-tolerant, ideal for microservices.
Q1: What is Apache Kafka?
-
Kafka is a distributed streaming platform used for high-throughput, fault-tolerant, real-time data pipelines.
-
It can be used as:
-
Messaging system (like RabbitMQ)
-
Storage system (durable logs)
-
Stream processing (via Kafka Streams or consumer apps)
Q2: What are the main components of Kafka?
-
Producer → sends messages to Kafka topics.
-
Consumer → reads messages from Kafka topics.
-
Broker → Kafka server that stores messages.
-
Topic → category/feed to which messages are published.
-
Partition → a topic can be split into partitions for scalability.
-
ZooKeeper / KRaft → manages cluster metadata (Kafka 2.x uses ZK; Kafka 3.x can use KRaft).
Q3: What is a Kafka topic and partition?
-
Topic → logical channel to which messages are sent.
-
Partition → physical division of a topic; helps in parallelism & scalability.
-
Offset → each message in a partition has a unique ID (sequence number).
Q4: What is an offset in Kafka?
-
A unique sequential ID assigned to each message within a partition.
-
Consumers track offsets to know which message they have read.
Q5: Explain Consumer Groups
-
A consumer group allows multiple consumers to read from a topic in parallel.
-
Rules:
-
Each partition is consumed by only one consumer in a group.
-
Multiple consumers in a group = parallel processing.
-
Multiple consumer groups = independent processing.
Q6: What is Kafka retention?
-
Kafka can retain messages based on:
-
Time (e.g., 7 days)
-
Size (e.g., 10GB per topic)
-
-
Allows consumers to read at their own pace.
Q8: What is the difference between at-most-once, at-least-once, and exactly-once delivery?
-
At-most-once → message may be lost, but never duplicated.
-
At-least-once → message may be duplicated but never lost.
-
Exactly-once → message is neither lost nor duplicated.
🧩 Partition-Level Ordering
Messages are ordered within a partition: Kafka guarantees that messages sent to the same partition are stored and delivered in the exact order they were produced.
No global ordering across partitions: If a topic has multiple partitions, Kafka does not guarantee ordering across them. So if ordering is critical, you must design your system to use a single partition or ensure related messages go to the same one.
🛠 Producer Controls
Key-based partitioning: Producers can assign a key to each message. Kafka uses this key to consistently route messages to the same partition, preserving order for that key.
Synchronous sends: If a producer sends messages synchronously (waiting for acknowledgment before sending the next), it helps maintain strict order.
📦 Consumer Guarantees
Sequential reads: Consumers read messages from a partition in the order they were written.
Offset tracking: Kafka uses offsets to track the position of each consumer, ensuring they process messages in sequence.
Q10: Explain Kafka Producer Acknowledgments (acks)
-
acks=0
→ Producer does not wait for broker acknowledgment. (Fastest, may lose data) -
acks=1
→ Leader writes message, producer gets acknowledgment. -
acks=all
/acks=-1
→ All in-sync replicas (ISR) acknowledge → safest, slower.
🔒 Why Use It?
Ideal for critical data where durability matters more than latency.
Helps prevent data loss in case of broker crashes or network partitions.
Apache Kafka ensures message ordering through a combination of partitioning, producer behavior, and consumer guarantees. Here's how it works:
🧩 Partition-Level Ordering
Messages are ordered within a partition: Kafka guarantees that messages sent to the same partition are stored and delivered in the exact order they were produced.
No global ordering across partitions: If a topic has multiple partitions, Kafka does not guarantee ordering across them. So if ordering is critical, you must design your system to use a single partition or ensure related messages go to the same one.
🛠 Producer Controls
Key-based partitioning: Producers can assign a key to each message. Kafka uses this key to consistently route messages to the same partition, preserving order for that key.
Synchronous sends: If a producer sends messages synchronously (waiting for acknowledgment before sending the next), it helps maintain strict order.
📦 Consumer Guarantees
Sequential reads: Consumers read messages from a partition in the order they were written.
Offset tracking: Kafka uses offsets to track the position of each consumer, ensuring they process messages in sequence.
⚠️ Things to Watch Out For
Multiple partitions: If you use multiple partitions for scalability, you lose total ordering unless you implement custom logic.
Retries and failures: If a producer retries a failed message, it could arrive out of order unless handled carefully.
Consumer parallelism: If consumers process messages in parallel, you might need additional logic to preserve order during processing.
Want to dive deeper into how to design a Kafka topic for ordered processing or how to handle ordering in a distributed consumer group?
In Apache Kafka, setting acks = -1
(or more commonly, acks = all
) in the producer configuration means:
✅ Strongest Message Durability Guarantee
"acks = all" tells Kafka that the producer should wait for acknowledgment from all in-sync replicas before considering a message successfully sent.
This ensures that the message is fully replicated and won’t be lost even if the leader broker fails immediately after the write.
🔒 Why Use It?
Ideal for critical data where durability matters more than latency.
Helps prevent data loss in case of broker crashes or network partitions.
⚠️ Trade-offs
Higher latency: Waiting for multiple replicas takes longer than just waiting for the leader.
Throughput impact: Slightly reduced throughput compared to
acks=1
oracks=0
.
Quick Comparison
acks Value | Description | Durability | Latency |
---|---|---|---|
0 | No ack needed | Low | Very low |
1 | Leader ack only | Medium | Low |
-1 or all | All in-sync replicas ack | High | Higher |
Let me know if you want to explore how this interacts with retries, idempotence, or Kafka’s replication mechanics.
In Apache Kafka, ISR stands for In-Sync Replicas—a key concept that ensures data durability and fault tolerance in Kafka’s distributed architecture.
🧠 What Is an ISR?
An In-Sync Replica is a follower replica of a partition that is fully caught up with the leader replica.
Kafka maintains a dynamic list of these replicas for each partition. Only replicas in this list are eligible to become the new leader if the current one fails.
Apache Kafka ensures message ordering through a combination of partitioning, producer behavior, and consumer guarantees. Here's how it works:
🧩 Partition-Level Ordering
Messages are ordered within a partition: Kafka guarantees that messages sent to the same partition are stored and delivered in the exact order they were produced.
No global ordering across partitions: If a topic has multiple partitions, Kafka does not guarantee ordering across them. So if ordering is critical, you must design your system to use a single partition or ensure related messages go to the same one.
🛠 Producer Controls
Key-based partitioning: Producers can assign a key to each message. Kafka uses this key to consistently route messages to the same partition, preserving order for that key.
Synchronous sends: If a producer sends messages synchronously (waiting for acknowledgment before sending the next), it helps maintain strict order.
📦 Consumer Guarantees
Sequential reads: Consumers read messages from a partition in the order they were written.
Offset tracking: Kafka uses offsets to track the position of each consumer, ensuring they process messages in sequence.
⚠️ Things to Watch Out For
Multiple partitions: If you use multiple partitions for scalability, you lose total ordering unless you implement custom logic.
Retries and failures: If a producer retries a failed message, it could arrive out of order unless handled carefully.
Consumer parallelism: If consumers process messages in parallel, you might need additional logic to preserve order during processing.
Want to dive deeper into how to design a Kafka topic for ordered processing or how to handle ordering in a distributed consumer group?
In Apache Kafka, setting acks = -1
(or more commonly, acks = all
) in the producer configuration means:
✅ Strongest Message Durability Guarantee
"acks = all" tells Kafka that the producer should wait for acknowledgment from all in-sync replicas before considering a message successfully sent.
This ensures that the message is fully replicated and won’t be lost even if the leader broker fails immediately after the write.
🔒 Why Use It?
Ideal for critical data where durability matters more than latency.
Helps prevent data loss in case of broker crashes or network partitions.
⚠️ Trade-offs
Higher latency: Waiting for multiple replicas takes longer than just waiting for the leader.
Throughput impact: Slightly reduced throughput compared to
acks=1
oracks=0
.
Quick Comparison
acks Value | Description | Durability | Latency |
---|---|---|---|
0 | No ack needed | Low | Very low |
1 | Leader ack only | Medium | Low |
-1 or all | All in-sync replicas ack | High | Higher |
Let me know if you want to explore how this interacts with retries, idempotence, or Kafka’s replication mechanics.
In Apache Kafka, ISR stands for In-Sync Replicas—a key concept that ensures data durability and fault tolerance in Kafka’s distributed architecture.
🧠 What Is an ISR?
An In-Sync Replica is a follower replica of a partition that is fully caught up with the leader replica.
Kafka maintains a dynamic list of these replicas for each partition. Only replicas in this list are eligible to become the new leader if the current one fails.
🔁 How ISR Works
Leader and Followers: Each Kafka partition has one leader and multiple followers. The leader handles all reads and writes.
Replication: Followers replicate data from the leader. If they keep up within a configured lag threshold (
replica.lag.time.max.ms
), they stay in the ISR list.Lagging Replicas: If a follower falls too far behind, Kafka removes it from the ISR list to prevent stale data from being promoted.
Leader Election: If the leader fails, Kafka elects a new leader from the ISR list—ensuring the new leader has the most recent data.
⚙️ Key Configurations
Parameter | Purpose |
---|---|
min.insync.replicas | Minimum number of ISRs required to acknowledge a write for it to be considered successful. |
replica.lag.time.max.ms | Maximum time a follower can lag behind before being removed from ISR. |
Q12: How do you achieve exactly-once semantics in Kafka?
-
Enable idempotent producer (
enable.idempotence=true
). -
Use transactions for multiple partitions/topics.
🧪 1. Idempotent Producer
Kafka producers can be configured with
enable.idempotence=true
.This ensures that duplicate messages are not written, even if retries occur due to network issues or broker failures.
Kafka assigns a unique sequence number to each message from a producer, allowing brokers to detect and discard duplicates.
Q14: How do you handle consumer failure?
-
Kafka tracks offsets → when consumer restarts, it resumes from last committed offset.
-
Can use manual commit (
commitSync
/commitAsync
) for more control.
Q15: How is Kafka scalable?
-
Partitions → multiple consumers read in parallel.
-
Cluster → multiple brokers for fault tolerance.
-
Replication → ensures high availability.
✅ How to create KafkaProducer and KafkaConsumer in Java
Answer:
To create a KafkaProducer, I set up properties like server address, key/value serializers, and then use
new KafkaProducer<>(props)
to send messages. For KafkaConsumer, I set properties like group ID and deserializers, subscribe to topics, and use a loop to poll messages.
✅ How to set acks
, retries
, and batch.size
for producer
Answer:
I set
acks=all
to make sure the message is safely stored. I useretries=3
to retry sending if something fails. I setbatch.size
to control how many messages are sent together to improve performance.
✅ How to commit offsets manually vs automatically
Answer:
If I use enable.auto.commit=true
, Kafka saves the offset automatically.
For manual commit, I set enable.auto.commit=false
and use commitSync()
after processing each message. This gives me more control and avoids losing data.
✅ How to use KafkaTemplate in Spring Boot
Answer:
In Spring Boot, I use
KafkaTemplate
to send messages easily. I configure it in a class with@Bean
, and then I can callkafkaTemplate.send(topic, message)
to publish messages.
what happen when producver star producing message ?
🛠️ 1. Producer connects to Kafka
It uses the broker address (like
localhost:9092
) to connect to the Kafka cluster.
📦 2. Message is created
The producer prepares a message with a key, value, and topic name.
🚀 3. Message is sent
It sends the message using
send()
method.Kafka decides which partition the message goes to (based on key or round-robin).
✅ 4. Acknowledgment
If
acks=all
, the producer waits for confirmation that all brokers received the message.If
acks=0
, it doesn’t wait—just sends and moves on.
🔁 5. Retries if needed
If sending fails, and
retries
is set, it will try again.
📊 6. Batching and buffering
Kafka may group messages into batches to send more efficiently.
These are stored in a buffer before sending.
What is a microservice architecture, and how do you implement service discovery in Spring Boot?
🔹 1. What is Microservice Architecture?
-
Microservices = breaking a large application into small, independent services, each responsible for a specific business function.
-
Characteristics:
-
Independent deployment → each service can be deployed without affecting others.
-
Loosely coupled → services communicate via APIs (REST, messaging).
-
Scalable → each service can scale independently.
-
Technology agnostic → each service can use different tech stack if needed.
-
Example:
-
An e-commerce app:
-
User Service → manages users
-
Order Service → handles orders
-
Payment Service → handles payments
-
Services communicate asynchronously (via Kafka) or synchronously (via REST API).
-
🔹 2. Service Discovery
-
In microservices, services run on dynamic ports and may scale up/down.
-
Service Discovery helps locate available service instances dynamically.
2.1 How it works
-
Each service registers itself with a Service Registry.
-
When a service wants to call another service, it queries the registry to find the instance.
2.2 Tools
-
Eureka (Netflix OSS) → common in Spring Boot ecosystem
-
Consul / Zookeeper → alternatives
spring.application.name=user-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
Now the User Service registers with Eureka.
Other services can discover it using the service name instead of hard-coded URLs.
🔍 What Is Service Discovery?
Imagine you have many microservices—like Payment Service, User Service, and Notification Service—all running on different servers or containers. These services need to talk to each other, but their locations (IP and port) can change anytime due to scaling or redeployment.
Service discovery solves this by:
Automatically registering services when they start.
Allowing other services to look up where they are.
🧩 Key Components
Component | Role |
---|---|
Service Provider | The microservice that offers functionality (e.g., Payment Service). |
Service Registry | A database that keeps track of all active service instances. |
Service Consumer | The microservice that wants to use another service (e.g., User Service calling Payment Service). |
🧭 Two Main Approaches
Client-Side Discovery
The client (consumer) queries the service registry directly.
It chooses which instance to call and handles load balancing.
Example: Netflix Eureka with Ribbon.
Server-Side Discovery
The client sends a request to a load balancer.
The load balancer queries the registry and forwards the request.
Example: Kubernetes with its built-in service discovery.
💡 Why It Matters
Makes your system dynamic and scalable.
Reduces manual configuration.
Helps with load balancing and fault tolerance.
⚡ What Is a Circuit Breaker?
Just like an electrical circuit breaker stops the flow of electricity when there's a fault, a software circuit breaker stops requests to a failing service to prevent further damage.
🧠 Why Is It Used?
Microservices often depend on each other. If one service starts failing (e.g., it's slow or down), and others keep calling it, it can:
Overload the failing service even more
Cause delays or failures in the calling services
Lead to a domino effect of system-wide outages
The circuit breaker pattern helps by:
Detecting repeated failures
“Tripping” the circuit to stop further calls temporarily
Allowing recovery time before retrying
🛠️ Where Is It Used?
You’ll find circuit breakers in:
Cloud-native apps using microservices
API gateways to protect downstream services
Distributed systems where services rely on external APIs or databases
Popular tools and libraries:
Hystrix (by Netflix, now retired but still influential)
Resilience4j (modern Java library)
Istio (for Kubernetes service mesh)
Spring Cloud Circuit Breaker
✅ Circuit Breaker Concept
-
Problem:
-
Service A calls Service B.
-
Service B is down or slow.
-
Continuous calls overload Service B and may cascade failure to Service A → system instability.
-
-
Solution: Circuit Breaker Pattern
-
Acts like an electrical circuit breaker.
-
Monitors failures of service calls.
-
If failures exceed a threshold → breaker “trips” → stops further calls to the failing service.
-
After a timeout, it allows a few test calls to check if Service B is healthy.
-
If healthy → breaker closes → normal flow resumes.
🔹 Benefits
-
Prevents cascading failures.
-
Improves fault tolerance and system stability.
-
Works well with API gateways and microservices.
public class PaymentService {
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")
public String processPayment() {
// Simulate a call to another microservice
if(Math.random() > 0.5) {
throw new RuntimeException("Service B down");
}
return "Payment successful";
}
// Fallback method
public String fallback(Throwable t) {
return "Payment service unavailable, please try later";
}
}
application.yml
resilience4j.circuitbreaker:
instances:
paymentService:
registerHealthIndicator: true
slidingWindowSize: 5
failureRateThreshold: 50
waitDurationInOpenState: 10000
slidingWindowSize
: number of calls monitored.-
failureRateThreshold
: % of failures to trip the breaker. -
waitDurationInOpenState
: time before trying again.
Imagine slidingWindowSize = 10
. That means:
The circuit breaker tracks the last 10 calls.
If, say, 6 out of those 10 calls failed, the failure rate is 60%.
If this rate exceeds the configured
failureRateThreshold
, the circuit breaker opens (i.e., blocks further calls temporarily).
🛡️ What Is a Fallback Method?
A fallback method is a backup plan. If your main method fails (due to timeout, exception, or circuit breaker open), the fallback method is called instead. It helps your app stay responsive and avoid crashing.
Example use case: If your Payment Service is down, the fallback method might return a message like “Payment service is currently unavailable. Please try again later.”
🔗 Resilience4j Integration in Spring Boot
Resilience4j is a lightweight fault-tolerance library that supports:
Circuit Breaker
Retry
Rate Limiter
Bulkhead
TimeLimiter
1. CircuitBreaker 🛑
Protects your system from calling a failing service repeatedly.
What it does: Monitors the success/failure rate of calls.
If failures exceed a threshold, it opens the circuit and blocks further calls temporarily.
Fallbacks can be triggered when the circuit is open.
Think of it like a fuse box: if too many failures happen, it “trips” to prevent further damage.
2. Retry 🔁
Automatically retries failed calls before giving up.
What it does: If a call fails due to a transient issue (e.g., network glitch), it retries a few times.
You can configure max attempts, wait duration, and exceptions to retry.
Useful when failures are short-lived and likely to succeed on a second or third try.
3. TimeLimiter ⏱️
Sets a maximum time limit for a call to complete.
What it does: If a service takes too long (e.g., stuck or slow), it times out the call.
Prevents your thread from hanging indefinitely.
Often used with CompletableFuture or asynchronous calls.
Helps maintain responsiveness and avoid thread starvation.
🚀 JWT Authentication Flow in Spring Boot
1. User Logs In
The user sends a POST request with credentials (username/password).
Spring Security authenticates the user using a custom
UserDetailsService
.
2. Generate JWT
If authentication succeeds, a JWT is generated and returned to the client.
The token contains user details and expiration time.
3. Client Stores JWT
The client (e.g., browser or mobile app) stores the token (usually in localStorage or sessionStorage).
4. Client Sends JWT with Requests
For protected endpoints, the client sends the JWT in the
Authorization
header:
5. Validate JWT
A filter intercepts incoming requests.
The token is extracted, validated (signature, expiration), and user details are loaded.
If valid, Spring Security sets the authentication context.
6. Access Granted
The request proceeds to the controller.
If the token is invalid or expired, a 401 Unauthorized response is returned.
✅ JWT Authentication Flow in Spring Boot
-
Login / Authentication
-
User sends username and password to the authentication endpoint (
/login
). -
Spring Security authenticates the credentials.
-
-
JWT Creation
-
If valid, the server creates a JWT (JSON Web Token).
-
Token contains:
-
Username / user ID
-
Roles / authorities
-
Expiration time
-
-
Token is signed using a secret key.
-
-
Token Delivery
-
JWT is sent back to the client (usually in the Authorization header).
-
Client stores it in localStorage or sessionStorage (browser) or in mobile app storage.
-
-
Request Authorization
-
For each subsequent request, client sends the JWT in the
Authorization: Bearer <token>
header. -
Server validates the JWT:
-
Signature
-
Expiration
-
Roles / permissions
-
-
-
Access Granted / Denied
-
If valid → request proceeds.
-
If invalid / expired → server returns 401 Unauthorized.
-
🔑 Key Points for Interview
-
JWT is stateless → no server session storage.
-
Includes claims (username, roles, expiration).
-
Sent in Authorization header for each request.
-
Server validates signature and expiration.
-
Common in REST APIs and microservices for scalability.
✅ Caching Strategies in Spring Boot
-
-
Stored in the heap memory of the application.
-
Examples: Caffeine, Guava Cache
-
Fast access but not shared across multiple service instances.
-
-
Distributed Caching
-
Shared across multiple service instances.
-
Examples: Redis, Hazelcast, Memcached
-
Good for scaling horizontally in microservices.
-
-
Cache-Aside / Lazy Loading
-
Application checks cache first.
-
If missing, load from DB, then populate cache.
-
Example:
@Cacheable
in Spring Boot.
-
Feature | Redis | Caffeine |
---|---|---|
Type | Distributed cache | In-memory cache |
Scaling | Horizontal (multiple nodes) | Vertical only (single app heap) |
Eviction Policy | LRU (Least Recently Used) | Advanced: size, time-based, LRU |
Speed | Slightly slower (network access) | Very fast (in-process memory) |
Persistence | Optional (can persist data) | Not persistent |
Use Case | Multi-instance microservices | Single-instance high-speed cache |
When we say persistent data, we mean:
Data that is stored permanently (or long-term) in a database, file system, or disk.
It survives application crashes, server reboots, or power failures.
🧠 Example:
If you store user info in RAM, it’s not persistent—it’s gone when the app stops.
If you store it in a database or Redis with persistence enabled, it stays saved.
🔁 In Caching:
Persistent cache (like Redis with AOF or RDB) saves data to disk.
Non-persistent cache (like Caffeine) keeps data only in memory—fast but temporary.
⚙️ 1. Serialization and Deserialization Issues
Kafka needs to know how to convert your Java objects to bytes and back.
Challenges:
Mismatched serializers between producer and consumer
Complex object structures causing deserialization failures
Solutions:
Use
KafkaTemplate
with properSerializer
andDeserializer
classesPrefer formats like JSON or Avro with schema registry for consistency
Configure
ErrorHandlingDeserializer
to gracefully handle bad messages
🔄 . Message Ordering and Partitioning
Kafka guarantees order only within a partition.
Challenges:
Messages processed out of order across partitions
Uneven load distribution
Solutions:
Use a consistent partition key (e.g., user ID) to maintain order
Tune the number of partitions based on throughput and parallelism needs.
🧯 . Error Handling and Retries
Spring Boot apps need robust handling for failed message processing.
Challenges:
Infinite retry loops
Silent message drops
Solutions:
Configure retry policies and backoff strategies using
RetryTemplate
Use Dead Letter Topics (DLT) to capture failed messages for later inspection
Monitor consumer lag and error rates with tools like Prometheus + Grafana
🛡️ . Security and Authentication
Kafka clusters often require authentication and encryption.
Challenges:
Misconfigured SSL/SASL settings
Credential management across environments
Solutions:
Use Spring Boot’s support for Kafka security configs
Externalize credentials using Spring Cloud Config or Vault
Enable TLS encryption and SASL authentication for secure communication.
📊 Monitoring and Observability
Kafka is fast—but debugging it can be a nightmare without visibility.
Challenges:
Hard to trace message flow across microservices
Limited built-in metrics
Solutions:
Integrate Micrometer with Spring Boot for Kafka metrics
Use distributed tracing tools like Zipkin or OpenTelemetry
Enable Kafka’s JMX metrics and monitor with Grafana or Datadog
Scaling Issues
-
Problem: High volume → uneven partition load.
-
Solution:
-
Proper partition strategy for topics
-
Scale consumers horizontally across instances.
-
✅ Common Challenges with Kafka in Spring Boot & Solutions
-
Message Lag / Backpressure
-
Problem: Consumers can’t keep up with producers → messages pile up.
-
Solution:
-
Tune consumer concurrency (
@KafkaListener(concurrency = "3")
) -
Increase consumer throughput
-
Monitor lag with Kafka monitoring tools (Kafka Manager, Prometheus + Grafana)
-
-
-
Delivery Failures / Retry Issues
-
Problem: Message fails due to temporary network/db issue.
-
Solution:
-
Use retry mechanism in Spring Kafka (
RetryTemplate
) -
Configure dead-letter topic (DLT) for messages that fail after max retries.
-
-
-
Security / Encryption
-
Problem: Kafka clusters need secure communication (SSL/TLS, SASL).
-
Solution:
-
Enable SSL/TLS encryption between producer, broker, and consumer
-
Configure authentication & authorization properly.
-
-
-
Monitoring & Observability
-
Problem: Hard to trace failures or message delays in production.
-
Solution:
-
Use centralized monitoring tools like Prometheus + Grafana or Kafka Manager
-
Log message metadata for traceability.
-
-
-
Scaling Issues
-
Problem: High volume → uneven partition load.
-
Solution:
-
Proper partition strategy for topics
-
Scale consumers horizontally across instances.
-
-
🔹 Designing a Spring Boot Microservice with Redis + Kafka
1️⃣ Use Case Example
-
Imagine a loan microservice in mPokket:
-
Users request a loan application.
-
The service needs fast access to user data and asynchronous processing for notifications or risk scoring.
-
2️⃣ Components
-
Spring Boot Service → handles loan requests.
-
Redis Cache → stores frequently accessed data (e.g., user profile, loan limits) to reduce DB calls.
-
Kafka Producer → sends events (e.g.,
LoanCreated
) to other services asynchronously. -
Kafka Consumer → listens to events from other services (e.g.,
CreditScoreUpdated
) to update data.
3️⃣ Flow
-
Incoming Request: User submits loan request via REST API.
-
Cache Lookup:
-
Check Redis for user data.
-
If present → use cached data.
-
If not → fetch from DB, then store in Redis (
@Cacheable
).
-
-
Business Processing: Validate request, calculate eligibility, store in DB.
-
Event Publishing:
-
Use Kafka Producer to publish
LoanCreated
event. -
Other microservices (notification, credit scoring) consume it asynchronously.
-
-
Response: Return loan approval/rejection to the user.
4️⃣ Benefits
Feature | Benefit |
---|---|
Redis Caching | Reduces DB load, improves response time |
Kafka Messaging | Asynchronous processing, decouples services |
Microservices Design | Independent, scalable, maintainable |
Event-Driven Flow | Resilient to failures; other services can react without blocking |
⚙️ What Is Asynchronous Processing?
Asynchronous processing means your method runs in the background, allowing the main thread to continue without waiting. This is useful when:
You don’t need an immediate result
- The task takes time (e.g., sending emails, calling external APIs, processing files)
✅ Asynchronous Processing in Spring Boot
-
Purpose
-
Allows a method to run in a separate thread without blocking the caller.
-
Useful for long-running tasks like sending emails, processing files, or calling external services.
-
-
Implementation with
@Async
@Async
in Spring Boot to run non-blocking tasks like email notifications or external service calls. It improves performance and user experience by freeing up the main thread. I enable it with @EnableAsync
and annotate methods I want to run in the background.”🚀 How to Implement @Async
in Spring Boot
✅ 1. Enable Async Support
Add this annotation to your configuration class:
@Configuration
public class AsyncConfig {
}
@Async
on Methodspublic class NotificationService {
@Async
public void sendEmail(String to, String message) {
// Simulate delay
Thread.sleep(3000);
System.out.println("Email sent to " + to);
}
}
private NotificationService notificationService;
public void triggerEmail() {
notificationService.sendEmail("user@example.com", "Welcome!");
System.out.println("Main thread continues...");
}
sendEmail()
method runs in a separate thread, so the main thread doesn’t wait for it to finish.🔹 Asynchronous Kafka Producer in Spring Boot
1️⃣ Concept
-
Normally, Kafka producer.send() is already asynchronous, but combining it with
@Async
in Spring Boot ensures that the calling thread is not blocked at all. -
Useful for high-throughput systems, like fintech apps, where you don’t want API response to wait for Kafka delivery.
3️⃣ Key Points for Interview
-
@Async
+KafkaTemplate.send()
→ completely non-blocking. -
Kafka is event-driven, so combining async helps high throughput.
-
Always handle success/failure callbacks to log or retry failed messages.
-
Useful in fintech scenarios where user requests should not wait for background processing.
🔹 Handling Kafka Consumer Failures with Retries & DLQ
1️⃣ Problem
-
Sometimes a consumer fails to process a message (e.g., invalid data, DB down).
-
If unhandled, the message may block the consumer or cause data loss.
2️⃣ Solution
-
Retries
-
Automatically retry consuming the message a fixed number of times.
-
Spring Kafka provides
SeekToCurrentErrorHandler
orDefaultErrorHandler
in newer versions. -
Example: retry 3 times before moving the message to DLQ.
-
-
Dead-Letter Queue (DLQ)
-
If retries fail, the message is sent to a special topic (DLQ) for later inspection.
-
Ensures no data is lost and failures can be analyzed and fixed.
3. DLQ Handling
The failed message is sent to the Dead Letter Queue.
The DLQ is a separate queue/topic where you can inspect or reprocess failed messages.
Processing DLQ Messages
DLQ messages are not automatically retried unless you explicitly set up a consumer for the DLQ.
You can:
Manually inspect and fix the data.
Reprocess messages by moving them back to the main queue.
Set up a separate consumer to handle DLQ messages with custom logic.
✅ 1. Use Pagination for Large Queries
Don’t load thousands of records at once.
✅ 2. Avoid N+1 Query Problem
Happens when JPA loads related entities one by one.
Use
@EntityGraph
orJOIN FETCH
to load related data in a single query.
✅ 3. Use Indexes in PostgreSQL
Indexes make searches faster.
Add indexes to frequently queried columns like
email
,created_at
, etc.Example:
@Column
+@Index
in JPA or native SQL indexing.
✅ 4. Batch Inserts and Updates
JPA by default sends one query per record. That’s slow.
Enable batching:
Use
saveAll()
instead ofsave()
in loops.
✅ 7. Caching Frequently Used Data
Avoid hitting the database for the same data repeatedly.
Use Spring Cache with Redis or Caffeine:
✅ 8. Profile Your Queries
Use tools like Hibernate Statistics, pg_stat_statements, or Spring Actuator to find slow queries.
Enable SQL logging:
✅ 6. Connection Pooling
Reusing DB connections improves performance.
Use HikariCP (default in Spring Boot).
Tune settings:
🧩 1. Batch Inserts and Updates
🔍 What it means:
Instead of sending one SQL query for each record, you send multiple records together in a single batch. This reduces the number of round-trips to the database and improves performance.
🧠 Why it's important:
Imagine inserting 1000 users. Without batching, JPA sends 1000 separate queries. With batching, it sends maybe 10 queries with 100 users each.
✅ How to enable it in Spring Boot:
Add these properties in application.properties
:
🧩 3. Connection Pooling
🔍 What it means:
Instead of opening and closing a new database connection every time, a pool of connections is kept ready. When your app needs a connection, it borrows one from the pool.
🧠 Why it's important:
Opening a DB connection is slow. Pooling makes it fast and efficient.
🧩 2. Use DTOs Instead of Entities for Read-Only Data
🔍 What it means:
DTO = Data Transfer Object. It’s a simple class with only the fields you need. You use it when you don’t need the full entity or don’t want to load relationships.
🧠 Why it's important:
Entities can be heavy—they may load related tables, lazy proxies, etc. DTOs are lightweight and faster for read-only operations.
explain scneario when you implement multhreading and address concurrency issue in your project ?
🛠️ Scenario: Building a File Upload Service
I was working on a backend service that allowed users to upload large files. The system had to:
Accept multiple uploads at the same time
Process each file (e.g., virus scan, metadata extraction)
Save results to a shared database
⚠️ Concurrency Issue
The problem came when multiple threads tried to write to the database at the same time. Sometimes:
Data got overwritten
Incomplete records were saved
Race conditions caused weird bugs
✅ How I Solved It
Here’s how I addressed the concurrency issues:
1. Synchronized Access
I used synchronization (e.g., synchronized
blocks in Java or locks in Python) to make sure only one thread could write to the database at a time.
2. Thread-safe Queues
I introduced a thread-safe queue where threads placed processed data, and a single thread handled database writes.
3. Connection Pooling
Used a connection pool to manage database connections efficiently without collisions.
4. Atomic Operations
For counters and flags, I used atomic variables to avoid race conditions.
Example 2-
✅ Scenario where I implemented Multithreading
Suppose I was working on a payment processing system (UPI/Card settlement) where multiple transactions come at the same time.
-
If we process them sequentially, response time would be very high.
-
So, we implemented multithreading using
ExecutorService
orCompletableFuture
to handle parallel requests (for example, validating transactions, fraud checks, updating balances).
👉 Example:
-
Thread 1: Validate card details
-
Thread 2: Fraud detection
-
Thread 3: Balance deduction and DB update
This way, multiple tasks run concurrently, reducing processing time.
✅ Concurrency Issue faced
While processing transactions in multiple threads, we faced race conditions:
-
Two threads were trying to update the same user account balance at the same time.
-
Example:
-
User A has ₹1000.
-
Transaction 1 deducts ₹200.
-
Transaction 2 deducts ₹500.
-
Without concurrency handling → both threads read balance = 1000, and both try to update → final balance may become 500 instead of 300.
-
This is called a concurrency issue.
✅ How I solved it
We used multiple techniques depending on the case:
-
Synchronized blocks / Locks
-
In Java, used
synchronized
orReentrantLock
to ensure only one thread updates the account at a time. Atomic variables
-
For counters/logs → used
AtomicInteger
instead of normalint
to avoid race conditions.
-
-
Optimistic Locking (Database level)
-
Used Hibernate @Version annotation (Optimistic Locking) so that if two threads try to update the same row, one will fail and retry.
-
-
Thread-safe collections
-
Instead of
HashMap
/ArrayList
, usedConcurrentHashMap
andCopyOnWriteArrayList
.
-
✅ Final Answer (Interview Style)
"In my payments project, we had a scenario where multiple threads were processing transactions in parallel for performance improvement. The issue we faced was concurrency while updating account balances — sometimes two threads tried updating the same account simultaneously, leading to incorrect balances. To handle this, we used synchronization and database-level optimistic locking with Hibernate’s @Version to ensure data consistency. For counters and logs, we used AtomicInteger, and for caching, we used thread-safe collections like ConcurrentHashMap. This ensured both high performance and data integrity."
🏦 Payment Project Example with Design Patterns
1. Singleton Pattern (One instance shared everywhere)
👉 When to use: For shared resources (connection pools, cache clients, logger).
Example in Payment Project:
-
Only one Redis Cache Client or Database Connection Pool should exist.
-
If multiple are created → memory wastage, inconsistent state.
2. Factory Pattern (Decide which object to create)
👉 When to use: When we want to create objects dynamically without many if else.
Example in Payment Project:
-
A user can pay with UPI, Card, Net Banking.
-
Instead of hardcoding logic, we use a PaymentFactory to return the right payment method.
3. Strategy Pattern (Multiple algorithms, choose one at runtime)
👉 When to use: When you have multiple algorithms for a task, and you want to switch at runtime.
Example in Payment Project:
-
Different discount/coupon strategies.
-
New User → 10% Off
-
Festival Offer → 20% Off
-
No Discount → 0%
-
4. Observer Pattern (One-to-many notification)
👉 When to use: When one event should trigger multiple actions automatically.
Example in Payment Project:
-
When a transaction is successful:
-
Send Email
-
Send SMS
-
Push to Kafka for audit
5. Builder Pattern (Step-by-step object creation)
👉 When to use: When object has too many parameters → instead of messy constructor, build it step by step.
Example in Payment Project:
-
When creating a Payment Request object with many optional fields:
🎯 Easy Summary (Interview One-liners)
-
Singleton → I used it for DB connection pool / Redis client → ensures single instance across app.
-
Factory → I used it in Payment system to create different payment methods dynamically.
-
Strategy → I applied it for different discount/coupon rules at checkout, selected at runtime.
-
Observer → I used it for notification system → one payment triggers Email, SMS, Kafka event.
-
Builder → I used it for creating complex
PaymentRequest
objects step by step with optional fields.
can you provide use case where you use static and non static variable in your project ?
💳 Use Case: Transaction Processing System
Imagine you're building a backend service that handles UPI transactions. You need to track global metrics (like total transactions) and also manage individual transaction details.
🔁 Static Variable: Global Transaction Counter
Purpose: Track how many transactions have been processed across the entire system.
public class TransactionManager { // Static variable shared across all instances public static int totalTransactions = 0; public static void incrementTransactionCount() { totalTransactions++; } public static int getTotalTransactions() { return totalTransactions; } }
✅ Why static? Because this counter is shared globally and doesn’t depend on any specific transaction object.
📊 Useful for analytics, monitoring, or load balancing.
🔒 Static Variable
Belongs to the class, not to any specific object.
Shared across all instances of that class.
Only one copy exists in memory.
📦 Non-Static Variable
Belongs to an individual object.
Each object has its own copy.
Used to store data that’s unique to each instance.
📦 Non-Static Variable: Individual Transaction Details
Purpose: Store data specific to each transaction, like amount, sender, receiver, and timestamp.
✅ Why non-static? Because each transaction has its own unique data.
🧾 Used for logging, auditing, and user notifications.
===========================
📌 What is a Collection in Java?
A Collection in Java is a framework (a set of classes & interfaces) that provides an architecture to store, manipulate, and retrieve groups of objects efficiently.
-
Located in
java.util
package. -
It does not deal with primitive types (int, double, etc.), only objects.
👉 But autoboxing helps (e.g.,int
→Integer
).
📌 Why Collection Framework?
Before Java 2 (JDK 1.2), developers used arrays, Vectors, Hashtables, but they had problems:
-
Fixed size (array).
-
Lack of built-in sorting, searching, iteration.
-
Different classes with different methods (no uniformity).
Collection Framework solved this by providing:
-
Interfaces → Set, List, Queue, Deque.
-
Implementations → ArrayList, HashSet, LinkedList, PriorityQueue, etc.
-
Algorithms → Sorting, searching (
Collections.sort()
,Collections.binarySearch()
).
ArrayList = Fast access, slow insert/delete
LinkedList = Slow access, fast insert/delete
ArrayList is based on dynamic array → so it gives fast random access (O(1)) but insertion/deletion in middle is costly (O(n)) because elements need shifting.
LinkedList is based on a doubly linked list → so it gives fast insertion/deletion (O(1)) if we already have node reference, but random access is slow (O(n)) since it must traverse nodes.
ArrayList vs Vector in Java
1. Synchronization
-
ArrayList → ❌ Not synchronized → faster (but not thread-safe).
-
Vector → ✅ Synchronized → thread-safe, but slower.
✅ What is a Functional Interface?
A Functional Interface is an interface that contains exactly one abstract method.
-
It can have multiple default methods and static methods (these are not abstract).
-
Functional Interfaces are the foundation of lambda expressions and method references in Java 8+.
Functional Interface = 1 abstract method + multiple default/static methods allowed.
✅ What if we have more than one abstract method?
-
If you add more than one abstract method, the compiler will throw an error saying:
✅ Easy Interview Answer
A Functional Interface can only have one abstract method because it’s designed to work with lambdas, which can implement only one method.
If you add more abstract methods, it’s just a normal interface, not a functional one, and can’t be used with lambdas.
✅ What is Dependency Injection?
👉 Dependency Injection means giving the objects (dependencies) that a class needs from outside, instead of the class creating them itself.
-
Without DI → Class creates its own dependencies (tight coupling).
-
With DI → Dependencies are "injected" (supplied) by a container like Spring (loose coupling).
✅ When does Spring create beans of the dependencies?
By default, in Spring, beans are created at application startup (container initialization).
-
If the bean scope is singleton (default) →
Spring creates the bean eagerly at startup, when the ApplicationContext is initialized. -
If the bean scope is prototype →
Spring creates a new bean only when you request it (getBean()
or when injected).
✅ Lazy Initialization
If you don’t want Spring to create beans at startup, you can use @Lazy
.
✅ Short Interview Answer
Spring creates beans:
-
By default, eagerly at container startup for singleton scope.
-
On demand for prototype scope.
-
You can use
@Lazy
to delay creation until first use.
-
✅ Short Interview Answer
If an interface has multiple implementations and we use @Autowired
on the interface, Spring cannot decide which bean to inject → it throws NoUniqueBeanDefinitionException
.
We resolve it using:
-
@Primary
→ mark one bean as default. -
@Qualifier
→ explicitly choose which bean to inject. -
Or match bean name with the variable name.
✅ Load Balancer
-
What it does → Distributes incoming traffic across multiple servers or service instances.
-
Goal → Scalability & high availability (no single server overloaded).
-
Works at → Network level (Layer 4) or Application level (Layer 7).
-
Examples → Nginx, HAProxy, AWS ELB, Azure Load Balancer.
✅ API Gateway
-
What it does → Acts as a single entry point for clients to access microservices.
-
Responsibilities:
-
Routing requests to correct microservice
-
Authentication & Authorization
-
Rate limiting / throttling
-
Logging / monitoring
-
Protocol translation (HTTP ↔ WebSocket, etc.)
-
-
Goal → Manage, secure, and route APIs.
-
Examples → Spring Cloud Gateway, Netflix Zuul, Kong, AWS API Gateway.
✅ Possible Answers
You can tailor depending on your project:
-
JDBC (Java Database Connectivity).
Hibernate (ORM)
JPA (Java Persistence API) with Hibernate implementation
Spring Data JPA
✅ Interview Answer (Ready to Say)
“In my projects, I’ve mostly used Spring Data JPA with Hibernate.
-
JDBC is low-level, requires writing queries and managing connections manually. Developer writes SQL manually ,
Manual Transaction handling with try/catch -
Hibernate is an ORM that maps Java objects to DB tables and auto-generates SQL. and manage transaction not using try catch.
-
JPA is just a specification; Hibernate is one of its implementations.
-
Spring Data JPA is built on JPA and Hibernate; it simplifies things further by providing repository interfaces for CRUD operations, so I can focus on business logic instead of boilerplate code.”
How do we define query in Spring Data Jpa?
✅ Ways to Define Queries in Spring Data JPA
1. Derived Query Methods (Method Name Convention)
Spring Data JPA can create queries just from method names in the repository interface.
public interface UserRepository extends JpaRepository<User, Long> {
// Derived queries
List<User> findByName(String name);
User findByEmail(String email);
List<User> findByAgeGreaterThan(int age);
}
✅ Why use @Query
in Spring Data JPA?
“In Spring Data JPA, most queries can be created automatically from method names like findByEmail
or findByAgeGreaterThan
. But sometimes we need custom or complex queries which cannot be handled by method names. In those cases, we use the @Query
annotation to define the query manually. So basically, simple queries = method names, complex/custom queries = @Query
.”
🔍 What is @Query
?
@Query
lets you write custom queries directly in your repository interface—no need to create separate query files.
@Query
annotation allows us to define custom queries directly in the repository interface. We can use it for both JPQL and native SQL queries.public interface UserRepository extends JpaRepository<User, Long> { @Query("SELECT u FROM User u WHERE u.email = ?1") User findByEmail(String email); }
User is the entity class
u.email is the field in that class
?1 means the first method parameter
✅ Kafka + Elasticsearch Together
Many companies use Kafka + Elasticsearch together in real-time systems:
-
Kafka collects and transports real-time data (e.g., user clicks, logs, transactions).
-
Elasticsearch stores and indexes this data for fast searching and analytics.
👉 Example (Interview scenario)
-
In a payments/UPI system:
-
All transactions events go into Kafka.
-
A consumer reads from Kafka and pushes data into Elasticsearch.
-
Elasticsearch allows support teams to search transactions quickly (by ID, user, status) and generate dashboards.
-
⚡ One-liner difference you can give in interview:
-
Kafka → “For moving/streaming data in real-time.”
-
Elasticsearch → “For searching and analyzing data quickly.”
✅ 1. Why DB Optimization?
-
Databases become a bottleneck when:
-
High traffic
-
Complex queries
-
Large datasets
-
-
Optimizing DB improves performance, scalability, and cost.
✅ 2. Common DB Optimization Areas (Interview Friendly)
🔹 (A) Query Optimization
-
Use Indexes
-
Helps speed up SELECT queries.
-
Example: Instead of scanning entire table → index finds rows faster.
-
Types: B-Tree Index, Hash Index, Composite Index.
-
👉 Interview point: "I create indexes on columns used in WHERE, JOIN, ORDER BY."
-
-
**Avoid SELECT ***
-
Only fetch required columns.
-
Bad:
SELECT * FROM users;
-
Good:
SELECT id, name FROM users;
-
-
Joins Optimization
-
Use proper join order.
-
Avoid joining too many tables unnecessarily.
-
-
Limit Data
-
Use
LIMIT
for pagination instead of fetching everything.
🔹 (B) Schema Design
-
Normalization
-
Removes redundancy (1NF, 2NF, 3NF).
-
Example: Store user address in a separate table.
-
-
Denormalization
-
For read-heavy systems, store some duplicate data to avoid joins.
-
Example: Store user’s full name in loan table.
-
-
Partitioning / Sharding
-
Split large tables into smaller ones (e.g., by region or time).
Q: What is Indexing?
👉 "Index is like a book index. Instead of scanning full table, DB uses B+ tree index to find rows faster. It improves read performance but slows down writes. We create indexes on frequently searched/joined columns."Q: Difference between Join and Subquery?
👉 "Both combine data, but a join merges tables horizontally, whereas subquery is a query inside another. Subqueries are sometimes slower; joins are often optimized better."Q: When to use Window Functions?
👉 "When I need row-wise analytics without grouping rows. For example, finding top-N salaries per department or running totals." C. Window Functions
Used for ranking, running totals, partitions.
-
B. Subquery
-
A query inside another query.
👉 Example: Find employees earning more than average salary:
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
1. What is an Index?
-
An index is like a book index. Instead of scanning the whole table, DB uses index to jump directly to the data.
-
Implemented internally using B+ Trees (most common) or Hash Indexes.
2. How it Works
-
Without index: DB scans all rows (full table scan).
-
With index: DB goes through the index tree, finds row pointer quickly.
🔹 Types of Indexes
-
Primary Index → Automatically on Primary Key.
-
Unique Index → Ensures uniqueness.
-
Composite Index → On multiple columns
(first_name, last_name)
. -
Clustered Index → Actual table rows stored in sorted order (only 1 per table).
-
Non-Clustered Index → Separate structure that points to table rows.
✅ Q2: SQL Questions
A. Joins
-
INNER JOIN → Only matching rows.
-
LEFT JOIN → All from left + matching right.
-
RIGHT JOIN → All from right + matching left.
-
FULL OUTER JOIN → All rows from both, unmatched = NULL.
The SQL GROUP BY Statement
The GROUP BY
statement groups rows that have the same values into summary rows, like "find the number of customers in each country".
The GROUP BY
statement is often used with aggregate functions (COUNT()
, MAX()
, MIN()
, SUM()
, AVG()
) to group the result-set by one or more columns.
The SQL HAVING Clause
The HAVING
clause was added to SQL because the WHERE
keyword cannot be used with aggregate functions.
Comments
Post a Comment