Interview Questions

Core Java (OOPs, Collections, Concurrency Basics)

Explain difference between HashMap, HashTable, and ConcurrentHashMap.

Feature HashMap Hashtable ConcurrentHashMap
Thread Safety Not thread-safe Thread-safe (synchronized methods) Thread-safe (lock segmentation / fine-grained locking)
Null Keys/Values Allows one null key and multiple null values Does not allow any null keys/values Does not allow null keys/values
Performance Faster (no synchronization overhead) Slower (entire table is locked) Better concurrency — multiple threads can read/write different segments simultaneously
Use Case Single-threaded apps Legacy thread-safe code High-performance concurrent apps

Example:
If multiple threads are accessing and modifying a map, prefer ConcurrentHashMap. For non-concurrent cases, use HashMap. Avoid Hashtable in modern code.

How does equals() and hashCode() work together in collections?

  • hashCode(): Returns an integer hash value used to place objects in hash-based collections (like HashMap, HashSet).
  • equals(): Compares two objects for logical equality.

Contract:

  • If two objects are equal according to equals(), they must have the same hashCode().
  • If two objects have the same hashCode(), they may or may not be equal.

Why it matters:
Collections like HashMap use hashCode() to find a bucket, and then equals() to check if the key already exists.
Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return id == p.id;
}

@Override
public int hashCode() {
return Objects.hash(id);
}

Difference between shallow copy vs deep copy in Java.

Type Description Example
Shallow Copy Copies only object references (not the objects they refer to). Both objects share the same nested objects. clone() without custom implementation
Deep Copy Copies everything — including new instances of nested objects. Changes in one object don’t affect the other. Implemented manually or via serialization

Example:

1
2
3
4
5
6
// Shallow copy
Employee e2 = e1.clone(); // e1.address and e2.address point to same object

// Deep copy
Employee e2 = new Employee(e1);
e2.address = new Address(e1.address);

How do you prevent deadlock in multithreaded applications?

Deadlock occurs when two or more threads are waiting on each other’s locks.

Ways to prevent it:

  1. Lock ordering – Always acquire locks in a consistent order.
  2. Try-lock with timeout – Use tryLock() from ReentrantLock to avoid waiting forever.
  3. Avoid nested locks – Reduce code sections that acquire multiple locks.
  4. Use concurrent utilities – Prefer ConcurrentHashMap, BlockingQueue, etc., which manage synchronization internally.
  5. Minimize synchronized blocks – Keep critical sections small.

Example:

1
2
3
4
5
if (lock1.tryLock(50, TimeUnit.MILLISECONDS)) {
if (lock2.tryLock(50, TimeUnit.MILLISECONDS)) {
// work
}
}

Explain volatile vs synchronized — when to use each.

Feature volatile synchronized
Purpose Guarantees visibility of changes across threads Guarantees atomicity and visibility
Use Case When multiple threads read/write a variable, and writes are independent When operations on shared data must be atomic
Locking No locking (lighter, faster) Uses intrinsic lock (can block threads)
Scope Variable-level only Code block or method
Example Use Status flags, configuration values Counters, complex state updates

Example:

1
2
3
4
5
volatile boolean running = true; // visibility only

synchronized void increment() { // atomic operation
count++;
}

Rule of thumb:

  • Use volatile for simple flags or status updates.
  • Use synchronized (or locks) for compound operations that must be atomic.

Spring & Spring Boot

Difference between @Component, @Service, @Repository, and @Controller.

Spring provides several stereotype annotations to define beans, which also give semantic meaning for better readability:

Annotation Purpose Special Behavior
@Component Generic stereotype for any Spring-managed component Base annotation for all other stereotypes
@Service Indicates a service layer component (business logic) Mainly semantic, no extra behavior, improves readability
@Repository Indicates a DAO (Data Access Object) component Converts database exceptions into Spring’s DataAccessException
@Controller Indicates a web controller handling HTTP requests Typically used with @RequestMapping to handle endpoints; works with Spring MVC

Summary:
All are detected via component scanning and registered as Spring beans, but @Repository and @Controller have additional framework-specific roles.

What is Spring Boot auto-configuration? How does it work internally?

Auto-configuration is a key feature of Spring Boot that automatically configures your application based on the dependencies on the classpath and properties defined in application.properties or application.yml.

How it works internally:

  1. Spring Boot uses the @SpringBootApplication annotation, which includes @EnableAutoConfiguration.
  2. @EnableAutoConfiguration imports spring.factories which lists all auto-configuration classes.
  3. During startup, Spring Boot evaluates each auto-configuration class using @Conditional annotations (e.g., @ConditionalOnClass, @ConditionalOnMissingBean) to decide which beans to create.
  4. Beans are registered automatically based on what classes and libraries are available on the classpath.

Example:
If spring-boot-starter-data-jpa is on the classpath, Spring Boot auto-configures EntityManagerFactory and DataSource beans automatically.

How do you implement exception handling globally in Spring Boot REST APIs?

Spring Boot provides @ControllerAdvice for global exception handling.

Steps:

  1. Create a class annotated with @ControllerAdvice.
  2. Define methods annotated with @ExceptionHandler to handle specific exceptions.
  3. Optionally, use @ResponseStatus or return a custom response body.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<String> handleGenericException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Something went wrong: " + ex.getMessage());
}
}

Explain difference between @RequestParam, @PathVariable, and @RequestBody.

Annotation Usage Example
@RequestParam Binds query parameters or form data GET /users?id=123@RequestParam("id") Long id
@PathVariable Binds URI template variables GET /users/123@PathVariable("id") Long id
@RequestBody Binds HTTP request body (JSON/XML) to Java object POST /users with JSON { "name": "John" }@RequestBody User user

Summary:

  • @RequestParam → query/form parameters
  • @PathVariable → path segments
  • @RequestBody → request body content

How do you implement profiles in Spring Boot for multiple environments?

Spring Boot supports profiles to separate environment-specific configurations (dev, test, prod).

Steps:

  1. Create profile-specific properties files:
    • application-dev.properties
    • application-prod.properties
  2. Activate a profile:
    • In application.properties:
      1
      spring.profiles.active=dev
    • Or via command line:
      1
      java -jar app.jar --spring.profiles.active=prod
  3. Use @Profile annotation to conditionally load beans:
    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @Profile("dev")
    public class DevConfig {
    @Bean
    public DataSource devDataSource() {
    return new HikariDataSource();
    }
    }
    Summary:
    Profiles allow you to maintain different configurations for different environments while keeping a single codebase.

JPA / Hibernate

What are entity states (Transient, Persistent, Detached, Removed) in Hibernate?

In Hibernate, an entity can exist in one of four states:

State Description Example
Transient The entity is not associated with a Hibernate Session and not persisted in the database. User user = new User(); (new object, no save() called)
Persistent The entity is associated with a Hibernate Session and changes will be automatically synchronized with the database. session.save(user);
Detached The entity was persistent, but the session is closed or entity is evicted. Changes are not automatically persisted. session.close(); user.setName("John");
Removed The entity is scheduled for deletion. It will be deleted when the transaction commits. session.delete(user);

Explain lazy loading and how to avoid LazyInitializationException.

Lazy loading is when Hibernate delays loading of associated entities until they are actually accessed.

  • Example:
    1
    2
    3
    4
    5
    @Entity
    class Department {
    @OneToMany(fetch = FetchType.LAZY)
    private List<Employee> employees;
    }
    employees are loaded from the database only when getEmployees() is called.

LazyInitializationException occurs if you try to access a lazily loaded collection outside an active session.

Ways to avoid it:

  1. Open Session in View (not recommended for all cases): Keep session open during view rendering.
  2. Fetch join in JPQL:
    1
    SELECT d FROM Department d JOIN FETCH d.employees WHERE d.id = :id
  3. Eager fetching (careful with performance):
    1
    @OneToMany(fetch = FetchType.EAGER)
  4. Initialize collection manually before session closes:
    1
    Hibernate.initialize(department.getEmployees());

Difference between Criteria API and JPQL.

Feature JPQL Criteria API
Query Type String-based queries Object-oriented, type-safe queries
Compile-time safety No Yes
Readability Simple for static queries Better for dynamic queries

Example

1
2
3
4
5
// SELECT u FROM User u WHERE u.age > 25
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
cq.select(user).where(cb.gt(user.get("age"), 25));

What is the difference between CascadeType.ALL and orphanRemoval?

Feature CascadeType.ALL orphanRemoval = true
Purpose Applies all operations (persist, merge, remove, refresh) from parent to child Deletes child entities when they are removed from parent collection
Example Saving parent automatically saves child Removing a child from parent’s collection deletes it from DB
Key Difference Cascade propagates operations, not necessarily removal Orphan removal only deletes orphans but doesn’t cascade persist

Example:

1
2
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees;

  • cascade = ALL → persisting parent also persists employees
  • orphanRemoval = true → removing employee from list deletes it from DB

How do you implement pagination and sorting in Spring Data JPA?

Spring Data JPA provides Pageable and Sort interfaces for pagination and sorting.

Example:

1
2
3
4
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByAgeGreaterThan(int age, Pageable pageable);
}

Usage in service:
1
2
3
4
5
Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending());
Page<User> users = userRepository.findByAgeGreaterThan(25, pageable);

users.getContent().forEach(System.out::println);
System.out.println("Total pages: " + users.getTotalPages());

  • PageRequest.of(page, size, sort) specifies page number, page size, and sorting.
  • Returns a Page which contains content, total pages, total elements, and more.

Microservices & Architecture

Explain difference between monolithic and microservices architectures.

Aspect Monolithic Architecture Microservices Architecture
Structure Single unified codebase Collection of small, independent services
Deployment One deployable unit Each service deployed independently
Scaling Scale entire application Scale individual services as needed
Technology Stack Usually uniform Can use different stacks per service
Fault Isolation Failure in one module may affect entire app Failures isolated to the specific service
Development Speed Slower for large teams Faster with independent teams

Summary:
Monolith = single large app; Microservices = many small, loosely coupled apps that communicate over APIs.

How do you implement inter-service communication? (REST, Kafka, gRPC)

Microservices need to communicate with each other. Common methods:

  1. REST (HTTP/JSON)
    • Simple, widely used, synchronous request-response.
    • Example:
      1
      2
      @GetMapping("/users/{id}")
      public User getUser(@PathVariable Long id) { ... }
  2. Kafka (Event-driven messaging)
    • Asynchronous, decoupled communication using topics.
    • Example: Service A produces events → Service B consumes events.
  3. gRPC (Remote Procedure Calls)
    • High-performance, strongly typed, binary protocol using Protocol Buffers.
    • Example:
      1
      2
      3
      service UserService {
      rpc GetUser(UserRequest) returns (UserResponse);
      }
      Summary:
  • REST → synchronous, simple
  • Kafka → asynchronous, decoupled
  • gRPC → synchronous or streaming, high performance

What is API Gateway and why do we need it?

An API Gateway is a single entry point for clients to interact with multiple microservices.

Responsibilities:

  • Routing requests to appropriate microservices
  • Request aggregation (combine multiple service responses)
  • Authentication & authorization
  • Rate limiting, caching, logging

Example:

  • Netflix Zuul, Spring Cloud Gateway, Kong, NGINX

Summary:
API Gateway simplifies client interaction, hides service complexity, and provides cross-cutting features in one place.

How do you manage configuration in multiple microservices?

For multiple services, externalized and centralized configuration is recommended.

Approaches:

  1. Spring Cloud Config
    • Central config server serving properties via Git/Filesystem.
    • Services fetch config dynamically.
  2. Environment variables / Kubernetes ConfigMaps & Secrets
    • Store environment-specific configuration outside code.
  3. Consul / etcd
    • Distributed configuration with service discovery integration.

Example with Spring Cloud Config:

1
2
3
4
5
6
spring:
application:
name: user-service
cloud:
config:
uri: http://config-server:8888

Explain circuit breaker pattern (Hystrix/Resilience4j) with example.

The circuit breaker pattern prevents cascading failures in microservices when one service is down or slow.

How it works:

  • Circuit is closed → calls pass normally.
  • If failures exceed threshold → circuit opens → further calls fail fast.
  • After a timeout → circuit half-open → test a few requests before closing.

Example with Resilience4j (Spring Boot):

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserService {

@CircuitBreaker(name = "userService", fallbackMethod = "fallbackGetUser")
public User getUser(Long id) {
// Call external service
}

public User fallbackGetUser(Long id, Throwable t) {
return new User(id, "Default User");
}
}

Summary:
Circuit breakers improve system resilience by preventing cascading failures and allowing graceful degradation.

Cloud & DevOps Basics

What is the difference between Docker image and Docker container?

Aspect Docker Image Docker Container
Definition Read-only template with application and dependencies Running instance of an image
Lifecycle Immutable, stored in registry Created, started, stopped, or destroyed
Purpose Used to build containers Executes the application
Example myapp:1.0 image docker run myapp:1.0 creates a container

Summary:
Image = blueprint; Container = running instance of that blueprint.

How does Kubernetes handle scaling and service discovery?

Scaling:

  • Kubernetes can automatically scale pods using:
    • Manual scaling: kubectl scale deployment myapp --replicas=5
    • Horizontal Pod Autoscaler (HPA): Automatically adjusts replicas based on CPU/memory metrics.

Service Discovery:

  • Pods are ephemeral; services use DNS names for discovery.
  • Kubernetes Service creates a stable IP and DNS name for a set of pods.
  • Example: http://user-service.default.svc.cluster.local

Explain difference between Blue-Green and Rolling deployments.

Aspect Blue-Green Deployment Rolling Deployment
Approach Two identical environments: Blue (current) & Green (new) Update pods gradually with new version
Downtime Near-zero downtime Minimal downtime during updates
Rollback Simple: switch traffic back to old environment Rollback requires reverting pods
Complexity Requires double resources Resource-efficient

How do you monitor Spring Boot microservices in production (Actuator, ELK, Prometheus)?

1. Spring Boot Actuator:

  • Exposes metrics, health checks, and info endpoints.
  • Example: /actuator/health, /actuator/metrics

2. ELK Stack (Elasticsearch, Logstash, Kibana):

  • Centralized logging for multiple microservices.
  • Collect logs with Logstash → store in Elasticsearch → visualize in Kibana.

3. Prometheus & Grafana:

  • Prometheus scrapes metrics from Actuator endpoints.
  • Grafana visualizes metrics in dashboards.
  • Example:
    1
    2
    3
    4
    5
    management:
    endpoints:
    web:
    exposure:
    include: health, metrics, prometheus

What is CI/CD? How have you implemented it in your projects?

CI/CD stands for Continuous Integration / Continuous Deployment.

  • Continuous Integration (CI): Automatically build and test code whenever changes are pushed.
  • Continuous Deployment (CD): Automatically deploy code to staging/production after passing tests.

Typical Implementation:

  1. Use GitHub Actions / Jenkins / GitLab CI to build, test, and package Spring Boot services into Docker images.
  2. Push Docker images to Docker Registry (Docker Hub, ECR, GCR).
  3. Deploy images to Kubernetes or cloud environment.
  4. Automated tests and monitoring ensure quality and quick rollback if needed.

Example GitHub Actions snippet:

1
2
3
4
5
6
7
8
9
10
11
name: CI/CD Pipeline
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Push to registry
run: docker push myapp:${{ github.sha }}

SQL / Database Knowledge

Write a SQL query to get department-wise average salary.

1
2
3
4
5
6
SELECT 
department_id,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
ORDER BY avg_salary DESC;

Explanation:

  • GROUP BY groups rows by department.
  • AVG() calculates the average salary per department.
  • ORDER BY sorts the results (optional).

Difference between INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN.

Type Description Example Result
INNER JOIN Returns only rows with matching values in both tables. Employees that belong to a department.
LEFT JOIN Returns all rows from the left table, even if no match exists in the right table. All employees, with department info if available.
RIGHT JOIN Returns all rows from the right table, even if no match exists in the left table. All departments, even those with no employees.
FULL JOIN Returns all rows from both tables, with NULL for missing matches. All employees and departments, matched where possible.

Example:

1
2
3
SELECT e.name, d.name AS department
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;

How do you optimize slow-running SQL queries?

  1. Analyze the query plan
    • Use EXPLAIN (MySQL, PostgreSQL) to view execution details.
    • Identify full table scans, missing indexes, or inefficient joins.
  2. Create appropriate indexes
    • Add indexes on columns used in WHERE, JOIN, and ORDER BY clauses.
  3. Reduce data retrieval
    • Use SELECT specific_columns instead of SELECT *.
    • Apply filters and limits.
  4. Optimize joins and subqueries
    • Prefer joins over nested subqueries when possible.
  5. Use caching
    • Cache frequent queries (e.g., Redis, Memcached).
  6. Partition large tables
    • Improves performance on very large datasets.

Explain ACID properties and isolation levels in databases.

ACID Properties

Property Description
Atomicity All operations in a transaction succeed or none do.
Consistency Database remains valid before and after the transaction.
Isolation Transactions do not interfere with each other.
Durability Once committed, data changes persist even after failures.

Isolation Levels

Level Description Common Problems Prevented
READ UNCOMMITTED Transactions can read uncommitted data (dirty reads). None
READ COMMITTED Prevents dirty reads, allows non-repeatable reads. Dirty reads
REPEATABLE READ Prevents dirty and non-repeatable reads, but phantom reads may occur. Dirty, non-repeatable reads
SERIALIZABLE Highest level, ensures full isolation (transactions run sequentially). All

How do you use indexes, and what are clustered vs non-clustered indexes?

What is an index?
An index improves data retrieval speed by allowing the database to find rows quickly, similar to a book index.

Syntax Example:

1
CREATE INDEX idx_employee_name ON employees(name);

Types of Indexes

Type Description Example
Clustered Index Physically sorts and stores table data based on the index key. One per table. PRIMARY KEY automatically creates a clustered index.
Non-Clustered Index Stores index separately from table data, containing pointers to the actual rows. Used for frequently queried columns not part of the primary key.

Summary:

  • Clustered = data sorted by index
  • Non-clustered = separate lookup structure

Best Practices:

  • Use indexes on columns often used in filters and joins.
  • Avoid over-indexing (it slows down inserts/updates).
  • Regularly monitor and rebuild fragmented indexes.

DSA & Coding (GlobalLogic Special Focus)

Write a program to reverse a linked list in Java (iterative + recursive).

Iterative Approach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Node {
int data;
Node next;
Node(int data) { this.data = data; }
}

public class ReverseLinkedList {

public static Node reverseIterative(Node head) {
Node prev = null, curr = head;
while (curr != null) {
Node next = curr.next; // store next
curr.next = prev; // reverse link
prev = curr; // move prev forward
curr = next; // move curr forward
}
return prev; // new head
}
}

Recursive Approach:
1
2
3
4
5
6
7
8
public static Node reverseRecursive(Node head) {
if (head == null || head.next == null)
return head;
Node newHead = reverseRecursive(head.next);
head.next.next = head;
head.next = null;
return newHead;
}

Implement a program to find the first non-repeating character in a string.

Logic: Use a frequency map to track occurrences, then find the first char with count = 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.*;

public class FirstNonRepeatingChar {
public static char firstNonRepeating(String s) {
Map<Character, Integer> map = new LinkedHashMap<>();
for (char c : s.toCharArray())
map.put(c, map.getOrDefault(c, 0) + 1);

for (Map.Entry<Character, Integer> entry : map.entrySet()) {
if (entry.getValue() == 1)
return entry.getKey();
}
return '_'; // if no non-repeating char
}

public static void main(String[] args) {
System.out.println(firstNonRepeating("swiss")); // Output: w
}
}

Write a Java program to find the longest common prefix among a set of strings.

Approach: Compare characters of all strings until mismatch occurs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LongestCommonPrefix {
public static String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) return "";
String prefix = strs[0];
for (int i = 1; i < strs.length; i++) {
while (!strs[i].startsWith(prefix)) {
prefix = prefix.substring(0, prefix.length() - 1);
if (prefix.isEmpty()) return "";
}
}
return prefix;
}

public static void main(String[] args) {
String[] input = {"flower", "flow", "flight"};
System.out.println(longestCommonPrefix(input)); // Output: "fl"
}
}

Implement LRU Cache in Java.

Approach: Use LinkedHashMap with access order.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.*;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;

public LRUCache(int capacity) {
super(capacity, 0.75f, true); // accessOrder = true
this.capacity = capacity;
}

@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}

public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "A");
cache.put(2, "B");
cache.put(3, "C");
cache.get(1); // access 1
cache.put(4, "D"); // removes 2 (least recently used)
System.out.println(cache.keySet()); // Output: [3, 1, 4]
}
}

Write a REST API to fetch last N orders of a customer (Spring Boot + JPA).

1
2
3
4
5
6
7
8
9
10
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long customerId;
private LocalDateTime orderDate;
private double amount;
// getters and setters
}
1
2
3
4
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findTopNByCustomerIdOrderByOrderDateDesc(Long customerId, Pageable pageable);
}
1
2
3
4
5
6
7
8
9
10
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;

public List<Order> getLastNOrders(Long customerId, int n) {
Pageable pageable = PageRequest.of(0, n);
return orderRepository.findByCustomerIdOrderByOrderDateDesc(customerId, pageable);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;

@GetMapping("/{customerId}/last")
public List<Order> getLastNOrders(
@PathVariable Long customerId,
@RequestParam(defaultValue = "5") int n) {
return orderService.getLastNOrders(customerId, n);
}
}

Example Request:

1
2
GET /api/orders/101/last?n=3
# Response: Returns the last 3 orders for customer 101 sorted by date (descending).