ACCENTURE INTERVIEW PREP
Custom Software Engineer — Spring Boot
Comprehensive Question Bank | Level 7–8 | 3+ Years Experience
SECTION 1: SPRING BOOT — CORE CONCEPTS
1.1 What is Spring Boot? How is it different from Spring Framework?
Q1: What is Spring Boot and how does it differ from the Spring Framework?
Answer:
Spring Framework is a comprehensive Java framework that requires extensive XML or Java-
based configuration to wire beans, set up DataSources, configure DispatcherServlet, etc. It gives
you full control but at the cost of boilerplate configuration.
Spring Boot is an opinionated, convention-over-configuration wrapper on top of Spring that
removes most of that boilerplate. It uses:
• Auto-Configuration: Automatically configures Spring beans based on what's on the
classpath (e.g., if H2 is present, it auto-configures an in-memory DataSource).
• Starter POMs: Curated dependency sets (spring-boot-starter-web, spring-boot-starter-data-
jpa) so you don't hunt for compatible versions.
• Embedded Servers: Ships with embedded Tomcat/Jetty/Undertow — no WAR deployment
needed.
• Actuator: Production-ready monitoring out of the box.
• Spring Initializr: Quick project bootstrapping at [Link].
Key difference: Spring Boot doesn't replace Spring — it simplifies it. Every Spring Boot app is a
Spring app, but not vice versa.
🔁 Possible Follow-up Questions:
➤ What is the role of @SpringBootApplication annotation?
➤ What does 'opinionated defaults' mean in Spring Boot context?
➤ Can you deploy a Spring Boot app as a WAR file?
➤ What is the difference between @SpringBootApplication and @EnableAutoConfiguration?
1.2 @SpringBootApplication Annotation
Q2: What does @SpringBootApplication do internally? What three annotations does it
combine?
Answer:
@SpringBootApplication is a convenience annotation that combines THREE annotations:
• @SpringBootConfiguration — Marks the class as a configuration class (like
@Configuration). Allows Spring to register additional beans via @Bean methods.
• @EnableAutoConfiguration — Tells Spring Boot to start adding beans based on classpath
settings, other beans, and various property settings. Uses [Link] or
spring/[Link] to load auto-
configuration classes.
• @ComponentScan — Enables scanning of the current package and sub-packages for
@Component, @Service, @Repository, @Controller, etc.
@SpringBootApplication(scanBasePackages = "[Link]", exclude =
{[Link]})
public class MyApp { public static void main(String[] args)
{ [Link]([Link], args); } }
Important: Place @SpringBootApplication on the main class at the ROOT package so component
scan covers all sub-packages.
🔁 Possible Follow-up Questions:
➤ What happens if you put @SpringBootApplication in a sub-package?
➤ How do you exclude a specific auto-configuration?
➤ What is [Link] file and where is it located?
➤ Can you have multiple @SpringBootApplication classes?
1.3 Spring Boot Auto-Configuration
Q3: Explain Spring Boot Auto-Configuration with an example. How does it work
internally?
Answer:
Auto-Configuration is the magic behind Spring Boot. Here's how it works step-by-step:
1. When the app starts, @EnableAutoConfiguration triggers SpringFactoriesLoader to read all
auto-configuration classes listed in:
META-INF/spring/[Link] (Boot
2.7+) or META-INF/[Link] (older versions).
2. Each auto-configuration class is annotated with conditional annotations like:
• @ConditionalOnClass — Only configure if a class is on the classpath
• @ConditionalOnMissingBean — Only configure if you haven't defined your own bean
• @ConditionalOnProperty — Only configure if a property is set
• @ConditionalOnWebApplication — Only in web context
3. Example — DataSourceAutoConfiguration:
@Configuration
@ConditionalOnClass({ [Link], [Link] })
@ConditionalOnMissingBean([Link])
public class DataSourceAutoConfiguration { ... }
This means: 'If DataSource class is on classpath AND no custom DataSource bean is defined,
auto-configure one.'
4. To see which auto-configurations are active: run with --debug flag or check
/actuator/conditions endpoint.
Tip: You can always OVERRIDE auto-config by defining your own bean — Spring Boot backs off
automatically.
🔁 Possible Follow-up Questions:
➤ How would you create your own custom auto-configuration?
➤ What is @Conditional annotation and how many variants exist?
➤ How do you debug which auto-configurations are being applied?
➤ What is the difference between @ConditionalOnBean and @ConditionalOnMissingBean?
➤ What is spring-boot-autoconfigure jar?
1.4 Spring Boot Starters
Q4: What are Spring Boot Starters? Name the most commonly used ones.
Answer:
Spring Boot Starters are curated dependency descriptors — pre-packaged sets of dependencies
needed for a specific use case, with guaranteed version compatibility.
You don't need to manually list each dependency and worry about version conflicts. Just add the
starter.
Most commonly used starters:
• spring-boot-starter-web — Spring MVC, REST APIs, embedded Tomcat, Jackson
• spring-boot-starter-data-jpa — Spring Data JPA, Hibernate, JDBC
• spring-boot-starter-security — Spring Security for authentication/authorization
• spring-boot-starter-test — JUnit 5, Mockito, Spring Test, AssertJ
• spring-boot-starter-actuator — Production monitoring endpoints
• spring-boot-starter-cache — Spring Cache abstraction
• spring-boot-starter-mail — JavaMail for email
• spring-boot-starter-thymeleaf — Thymeleaf templating engine
• spring-boot-starter-validation — Bean Validation (Hibernate Validator)
• spring-boot-starter-aop — Aspect-Oriented Programming
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
You can also create custom starters for your organization's common libraries.
🔁 Possible Follow-up Questions:
➤ How does spring-boot-starter-web differ from spring-boot-starter-webflux?
➤ Can you exclude a transitive dependency from a starter?
➤ How do you create a custom Spring Boot starter?
SECTION 2: SPRING BEANS, IoC & DEPENDENCY
INJECTION
2.1 Bean Lifecycle
Q5: Explain the complete lifecycle of a Spring Bean.
Answer:
Spring Bean lifecycle goes through these phases:
Phase 1 — Instantiation:
• Spring container reads configuration (annotations/XML), creates a bean instance using the
constructor.
Phase 2 — Populate Properties (Dependency Injection):
• Spring injects dependencies via @Autowired fields, setter injection, or constructor injection.
Phase 3 — BeanNameAware / BeanFactoryAware / ApplicationContextAware:
• If the bean implements these interfaces, Spring calls the respective setter methods, giving
the bean access to its name, BeanFactory, or ApplicationContext.
Phase 4 — [Link]():
• All BeanPostProcessors run their before-init logic (e.g., AOP proxy creation starts here).
Phase 5 — Initialization (@PostConstruct / [Link]() / init-
method):
• @PostConstruct method is called first, then afterPropertiesSet(), then custom init-method.
Phase 6 — [Link]():
• Proxies are fully created here (AOP advices applied).
Phase 7 — Bean Ready for Use:
• Bean is stored in application context and available for injection.
Phase 8 — Destruction (@PreDestroy / [Link]() / destroy-method):
• On context shutdown: @PreDestroy → destroy() → custom destroy-method.
@Component
public class MyBean implements InitializingBean, DisposableBean {
@PostConstruct public void init() { [Link]("Post Construct"); }
public void afterPropertiesSet() { [Link]("InitializingBean"); }
@PreDestroy public void cleanup() { [Link]("Pre Destroy"); }
public void destroy() { [Link]("DisposableBean"); }
}
🔁 Possible Follow-up Questions:
➤ What is the difference between @PostConstruct and InitializingBean?
➤ When would you use BeanPostProcessor?
➤ Does @PreDestroy get called for prototype beans?
➤ What happens if an exception is thrown in @PostConstruct?
2.2 Bean Scopes — DETAILED
Q6: Explain all Spring Bean Scopes in detail with real-world use cases.
Answer:
Spring provides 6 bean scopes (2 core + 4 web-aware):
1. Singleton (DEFAULT):
• ONE instance per Spring IoC container. The same object is returned every time it is
requested.
• Spring creates it at startup (eager) unless @Lazy is used.
• Use case: Stateless services (UserService, OrderRepository), configuration beans.
@Component @Scope("singleton") // or simply @Component
public class UserService { }
2. Prototype:
• A NEW instance is created every time the bean is requested from the container.
• Spring does NOT manage destruction of prototype beans — @PreDestroy is NOT called!
• Use case: Stateful beans (shopping cart object, command objects), beans that hold
request-specific data.
@Component @Scope("prototype")
public class ShoppingCart { private List<Item> items = new ArrayList<>(); }
3. Request (Web-aware):
• One instance per HTTP request. Created when request arrives, destroyed when response
is sent.
• Use case: Beans holding form data, request-specific computations.
@Component @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode =
ScopedProxyMode.TARGET_CLASS)
public class RequestContext { }
4. Session (Web-aware):
• One instance per HTTP session. Lives as long as the user session is active.
• Use case: User login info, shopping cart per user session.
@Component @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode =
ScopedProxyMode.TARGET_CLASS)
public class UserSession { private String username; }
5. Application (Web-aware):
• One instance per ServletContext (similar to singleton but per web application). Shared
across all sessions.
• Use case: Application-level counters, configuration shared globally.
6. WebSocket (Web-aware):
• One instance per WebSocket session.
CRITICAL CONCEPT — Scope Mismatch Problem:
Injecting a prototype/request-scoped bean into a singleton causes issues — the singleton is
created ONCE, so it will always hold the SAME prototype instance.
Solution: Use proxyMode = ScopedProxyMode.TARGET_CLASS or inject ApplicationContext
and call getBean() manually or use ObjectFactory<T> / Provider<T>.
@Autowired private ObjectFactory<ShoppingCart> cartFactory;
// In method: ShoppingCart cart = [Link](); // always a new instance
🔁 Possible Follow-up Questions:
➤ What problem does proxyMode solve? How does it work internally?
➤ What is the difference between singleton scope in Spring and the Singleton design pattern?
➤ Why doesn't @PreDestroy get called for prototype beans?
➤ If I inject a session-scoped bean into a singleton — what happens?
➤ How does Spring create scoped proxies? (CGLIB vs JDK proxy)
➤ What is the difference between Application scope and Singleton scope?
2.3 Types of Dependency Injection
Q7: What are the types of Dependency Injection in Spring? Which one is
recommended and why?
Answer:
Spring supports three types of Dependency Injection:
1. Constructor Injection (RECOMMENDED):
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) { // @Autowired optional if
single constructor
[Link] = paymentService;
}
}
• Dependencies are FINAL — immutable after construction
• Makes the bean fail fast — if dependency is missing, app won't start
• Easier to test — pass mock directly in constructor
• Avoids circular dependency issues (Spring will throw an error early)
2. Setter Injection:
@Service
public class OrderService {
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) { [Link]
= paymentService; }
}
• Use for OPTIONAL dependencies
• Allows re-injection (useful in testing)
• Dependency can be null at construction time
3. Field Injection (NOT recommended for production):
@Autowired
private PaymentService paymentService;
• Simple but has issues: can't be final, hides dependencies, harder to test (need reflection or
Spring context for testing)
Spring team recommends Constructor Injection as the primary approach.
🔁 Possible Follow-up Questions:
➤ How do you resolve circular dependency in Spring Boot?
➤ What is @Qualifier and when do you use it?
➤ What is the difference between @Autowired and @Inject?
➤ Can you inject a bean by name using @Autowired?
➤ What is @Primary annotation?
SECTION 3: REST API & SPRING MVC
3.1 REST API Annotations
Q8: Explain the key REST API annotations in Spring Boot: @RestController,
@RequestMapping, @GetMapping, @PostMapping, @PathVariable, @RequestParam,
@RequestBody
@RestController:
Combines @Controller + @ResponseBody. Every method returns data (usually JSON) directly,
not a view name.
@RequestMapping:
Maps HTTP requests to handler methods. Can be used at class level (base URL) and method
level.
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping / @PostMapping / @PutMapping / @DeleteMapping / @PatchMapping:
Shorthand for @RequestMapping(method = [Link]/POST/etc).
@PathVariable — extract from URL path:
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) { ... } // URL: /api/v1/users/5
@RequestParam — extract from query string:
@GetMapping("/search")
public List<User> search(@RequestParam(defaultValue="") String name,
@RequestParam(required=false) Integer age) { ... }
// URL: /api/v1/users/search?name=John&age=25
@RequestBody — deserialize JSON body:
@PostMapping
public ResponseEntity<User> create(@Valid @RequestBody UserRequest request) { ... }
@ResponseStatus — set HTTP status code:
@ResponseStatus([Link]) // 201
ResponseEntity<T>:
Gives full control over HTTP response including status code, headers, and body.
return [Link]([Link]).header("Location",
"/users/"+[Link]()).body(user);
🔁 Possible Follow-up Questions:
➤ What is the difference between @Controller and @RestController?
➤ How do you handle different content types (XML vs JSON) in the same endpoint?
➤ What is @RequestHeader and when do you use it?
➤ How do you handle multipart file uploads?
➤ What is content negotiation in Spring MVC?
🎯 SCENARIO: You have a REST endpoint /api/orders/{orderId}/items. The client wants to filter
by status (PENDING/COMPLETED) and paginate results. How do you design this endpoint?
@GetMapping("/api/orders/{orderId}/items")
public ResponseEntity<Page<OrderItem>> getItems(
@PathVariable Long orderId,
@RequestParam(required=false) String status,
@RequestParam(defaultValue="0") int page,
@RequestParam(defaultValue="10") int size,
@RequestParam(defaultValue="createdAt") String sortBy) {
Pageable pageable = [Link](page, size, [Link](sortBy));
Page<OrderItem> result = [Link](orderId, status, pageable);
return [Link](result);
}
// Call: GET /api/orders/10/items?status=PENDING&page=0&size=5&sortBy=createdAt
3.2 Global Exception Handling with @ControllerAdvice
Q9: How do you implement global exception handling in Spring Boot? Explain
@ControllerAdvice and @ExceptionHandler.
Answer:
@ControllerAdvice is a global cross-cutting concern class that handles exceptions thrown from
ANY controller. Combined with @ExceptionHandler, it provides centralized exception handling.
@RestControllerAdvice // = @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler([Link])
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(404, [Link](), [Link]());
return [Link](HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler([Link])
public ResponseEntity<Map<String,String>>
handleValidation(MethodArgumentNotValidException ex) {
Map<String,String> errors = new HashMap<>();
[Link]().getFieldErrors()
.forEach(e -> [Link]([Link](), [Link]()));
return [Link]().body(errors);
}
@ExceptionHandler([Link])
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
return [Link]()
.body(new ErrorResponse(500, "Internal Server Error", [Link]()));
}
}
Custom Exception class:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) { super(message); }
}
🔁 Possible Follow-up Questions:
➤ What is the difference between @ControllerAdvice and @RestControllerAdvice?
➤ How do you handle validation errors from @Valid?
➤ Can you have multiple @ControllerAdvice classes? Which takes priority?
➤ What is ProblemDetail in Spring 6 / Spring Boot 3?
➤ How do you return a custom error response with proper HTTP status codes?
SECTION 4: SPRING DATA JPA & DATABASE
4.1 Spring Data JPA — Core Concepts
Q10: What is Spring Data JPA? Explain CrudRepository vs JpaRepository vs
PagingAndSortingRepository.
Answer:
Spring Data JPA provides a repository abstraction layer over JPA. You define interfaces —
Spring generates the implementation at runtime.
Repository Hierarchy:
• Repository<T, ID> — Marker interface, no methods
• CrudRepository<T, ID> — Basic CRUD: save, findById, findAll, delete, count, existsById
• PagingAndSortingRepository<T, ID> — Adds findAll(Pageable), findAll(Sort)
• JpaRepository<T, ID> — Extends both above + JPA-specific: saveAll, flush, deleteInBatch,
getReferenceById
Use JpaRepository in practice — it gives you everything.
Derived Query Methods — Spring generates SQL from method names:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByEmail(String email);
List<User> findByAgeGreaterThanAndActiveTrue(int age);
Optional<User> findByEmailIgnoreCase(String email);
List<User> findTop5ByOrderByCreatedAtDesc();
Page<User> findByDepartment(String dept, Pageable pageable);
boolean existsByEmail(String email);
long countByActive(boolean active);
@Modifying @Query("UPDATE User u SET [Link] = false WHERE [Link] < :date")
int deactivateInactiveUsers(@Param("date") LocalDate date);
}
🔁 Possible Follow-up Questions:
➤ What is @Query annotation? When do you use it over derived methods?
➤ What is the difference between JPQL and native queries?
➤ How does @Transactional work in Spring Data JPA?
➤ What is the N+1 problem and how do you solve it?
➤ What is @EntityGraph and how does it help with lazy loading?
➤ What is the difference between findById and getReferenceById (formerly getOne)?
4.2 @Transactional — Deep Dive
Q11: Explain @Transactional in detail. What are its propagation levels and isolation
levels?
Answer:
@Transactional manages database transactions declaratively. Spring creates a proxy around the
bean, intercepting method calls to start/commit/rollback transactions.
Propagation Levels (what happens when a @Transactional method calls another
@Transactional method):
• REQUIRED (DEFAULT) — Join existing tx if present; create new one if not.
• REQUIRES_NEW — ALWAYS create a new transaction; suspend existing one. Useful for
audit logging (should commit regardless of outer tx).
• NESTED — Create a nested tx (savepoint) within existing tx. If nested fails, rollback to
savepoint only.
• SUPPORTS — Join tx if exists; run without tx if not.
• NOT_SUPPORTED — Always run without tx; suspend existing.
• MANDATORY — Must run within existing tx; throw exception if none.
• NEVER — Must NOT run within tx; throw exception if one exists.
Isolation Levels (what data changes from concurrent transactions are visible):
• READ_UNCOMMITTED — Can read dirty (uncommitted) data. Risk: dirty reads.
• READ_COMMITTED (DEFAULT in most DBs) — Only read committed data. Prevents dirty
reads.
• REPEATABLE_READ — Same row returns same data in same tx. Prevents non-repeatable
reads.
• SERIALIZABLE — Full isolation; transactions run sequentially. Slowest, safest.
@Transactional(propagation = Propagation.REQUIRES_NEW,
isolation = Isolation.READ_COMMITTED,
rollbackFor = [Link],
timeout = 30,
readOnly = true)
public void processPayment(PaymentRequest req) { ... }
Critical gotcha — @Transactional self-invocation problem:
Calling a @Transactional method from within the SAME class bypasses the proxy — transaction
does NOT start!
Solution: Inject the bean itself (self-injection), use AspectJ mode, or restructure code.
🔁 Possible Follow-up Questions:
➤ Why doesn't @Transactional work when calling a method within the same class?
➤ What is readOnly=true and how does it help performance?
➤ What is the default rollback behavior of @Transactional?
➤ Does @Transactional work on private methods?
➤ What is optimistic vs pessimistic locking in JPA?
➤ Explain the difference between REQUIRES_NEW and NESTED propagation.
4.3 N+1 Problem
Q12: What is the N+1 problem in JPA? How do you identify and fix it?
Answer:
The N+1 problem occurs when fetching a list of entities (1 query) and then for each entity, JPA
fires an additional query to load a lazy association (N queries). Total = N+1 queries.
Example: Fetch 100 Orders, each with lazy-loaded Customer → 1 (orders) + 100 (customers) =
101 queries.
How to identify:
• Enable SQL logging: [Link]-sql=true and
[Link].format_sql=true
• Use Hibernate Statistics or p6spy
Solutions:
1. JOIN FETCH in JPQL:
@Query("SELECT o FROM Order o JOIN FETCH [Link] WHERE [Link] = :status")
List<Order> findByStatusWithCustomer(@Param("status") String status);
2. @EntityGraph:
@EntityGraph(attributePaths = {"customer", "items"})
List<Order> findByStatus(String status);
3. Change FetchType to EAGER (use with caution — can cause over-fetching):
@ManyToOne(fetch = [Link])
4. Batch fetching: [Link].default_batch_fetch_size=20
This batches N lazy loads into ceil(N/20) queries instead of N.
Best practice: Use JOIN FETCH or @EntityGraph for specific queries where you know the
association is needed.
🔁 Possible Follow-up Questions:
➤ What is the difference between JOIN FETCH and @EntityGraph?
➤ Can @EntityGraph cause issues? (MultipleBagFetchException)
➤ What is the Cartesian product problem with multiple JOIN FETCHes?
➤ How does Hibernate batch fetching work?
SECTION 5: CONFIGURATION & PROFILES
5.1 [Link] vs [Link]
Q13: Explain Spring Boot configuration files. How do profiles work? What is @Value
and @ConfigurationProperties?
Configuration Files:
Spring Boot reads properties from multiple sources in order of priority (higher overrides lower):
1. Command line args: --[Link]=9090
2. JNDI attributes
3. OS environment variables: SERVER_PORT=9090
4. application-{profile}.properties/yml
5. [Link]/yml
Profiles — activating environment-specific config:
# [Link] (common)
[Link]=my-app
# [Link]
[Link]=jdbc:h2:mem:devdb
# [Link]
[Link]=jdbc:postgresql://prod-server/mydb
Activate: [Link]=dev (in properties) or --[Link]=prod (command line)
@Value — inject single property:
@Value("${[Link]-retries:3}") // 3 is default if property missing
private int maxRetries;
@ConfigurationProperties — bind whole group of properties to a POJO (PREFERRED
for multiple related properties):
@ConfigurationProperties(prefix = "app")
@Component // or use @EnableConfigurationProperties on main class
public class AppProperties {
private int maxRetries;
private String baseUrl;
private Duration timeout;
// getters + setters
}
# [Link]
[Link]-retries=5
[Link]-url=[Link]
[Link]=30s
Benefits of @ConfigurationProperties: Type-safe, IDE autocomplete, relaxed binding (max-retries
= maxRetries = MAX_RETRIES), easy to validate with @Validated.
🔁 Possible Follow-up Questions:
➤ What is relaxed binding in Spring Boot?
➤ How do you validate @ConfigurationProperties fields?
➤ What is @Profile annotation and how do you use it on beans?
➤ How do you externalize configuration in a Docker/Kubernetes deployment?
➤ What is Spring Cloud Config and when do you need it?
SECTION 6: SPRING BOOT ACTUATOR
6.1 Actuator Endpoints
Q14: What is Spring Boot Actuator? Which endpoints are important and how do you
secure them?
Answer:
Spring Boot Actuator exposes production-ready endpoints for monitoring and managing your
application.
Key endpoints:
• /actuator/health — App health status (UP/DOWN), disk, DB, external services
• /actuator/info — Custom app info (version, build details)
• /actuator/metrics — JVM metrics, HTTP request stats, custom metrics (integrates with
Micrometer)
• /actuator/env — Shows all environment properties (sensitive!)
• /actuator/beans — All Spring beans in the context
• /actuator/mappings — All @RequestMapping paths
• /actuator/conditions — Auto-configuration conditions report
• /actuator/loggers — View and change log levels at runtime
• /actuator/threaddump — Current thread dump
• /actuator/heapdump — JVM heap dump
• /actuator/prometheus — Prometheus metrics export
Configuration:
[Link]=health,info,metrics,loggers
[Link]-details=always
[Link]=8081 # separate port for actuator
Securing Actuator:
# In SecurityConfig
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
Custom Health Indicator:
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
public Health health() {
return externalApiUp() ? [Link]().withDetail("api","reachable").build()
: [Link]().withDetail("api","unreachable").build();
}
}
🔁 Possible Follow-up Questions:
➤ How do you create a custom Actuator endpoint?
➤ How do you integrate Actuator with Prometheus and Grafana?
➤ What is Micrometer and how does it relate to Actuator?
➤ How do you change the log level of a specific logger at runtime using Actuator?
SECTION 7: SPRING SECURITY (BASICS)
7.1 Spring Security with JWT
Q15: How do you implement JWT-based authentication in Spring Boot?
Answer:
JWT (JSON Web Token) authentication flow in Spring Boot:
Flow:
1. User sends POST /auth/login with credentials
2. App validates credentials, generates JWT token, returns it
3. Client sends JWT in Authorization header: 'Bearer <token>' for subsequent requests
4. JwtAuthenticationFilter intercepts each request, validates token, sets SecurityContext
Key Components:
// 1. JwtUtil — generate & validate tokens
@Component
public class JwtUtil {
public String generateToken(UserDetails user) {
return [Link]().setSubject([Link]())
.setIssuedAt(new Date())
.setExpiration(new Date([Link]() + 86400000))
.signWith(getSigningKey(), SignatureAlgorithm.HS256).compact();
}
public String extractUsername(String token) { ... }
public boolean isTokenValid(String token, UserDetails user) { ... }
}
// 2. JwtAuthenticationFilter
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) {
String authHeader = [Link]("Authorization");
if (authHeader != null && [Link]("Bearer ")) {
String token = [Link](7);
String username = [Link](token);
UserDetails user = [Link](username);
if ([Link](token, user)) {
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(user, null, [Link]());
[Link]().setAuthentication(auth);
}
}
[Link](req, res);
}
}
// 3. SecurityFilterChain
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return [Link]().disable()
.sessionManagement().sessionCreationPolicy([Link])
.and().authorizeHttpRequests()
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and().addFilterBefore(jwtAuthFilter, [Link])
.build();
}
🔁 Possible Follow-up Questions:
➤ What are the three parts of a JWT token?
➤ How do you handle JWT token expiration and refresh tokens?
➤ What is the difference between authentication and authorization?
➤ How do you implement role-based access control (RBAC)?
➤ What is @PreAuthorize and how do you use method-level security?
➤ Why do we disable CSRF for REST APIs?
SECTION 8: MICROSERVICES CONCEPTS
8.1 Microservices Communication
Q16: How do microservices communicate? Explain RestTemplate vs WebClient vs
OpenFeign.
Answer:
1. RestTemplate (Legacy — being deprecated):
Synchronous, blocking HTTP client.
RestTemplate restTemplate = new RestTemplate();
User user = [Link]("[Link] [Link]);
2. WebClient (Modern — Reactive, non-blocking):
Part of Spring WebFlux. Preferred even in non-reactive apps for its flexibility.
WebClient client = [Link]().baseUrl("[Link]
Mono<User> user = [Link]().uri("/users/{id}",
id).retrieve().bodyToMono([Link]);
User syncUser = [Link](); // blocking if needed in non-reactive context
3. OpenFeign (Declarative — RECOMMENDED for microservices):
Define an interface — Feign generates the implementation.
@FeignClient(name = "user-service", url = "[Link]
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable Long id);
@PostMapping("/users")
User createUser(@RequestBody UserRequest request);
}
@Service
public class OrderService {
@Autowired private UserServiceClient userClient;
public void placeOrder(Long userId) {
User user = [Link](userId); // feels like local method call
}
}
OpenFeign advantages: Integrates with Eureka (service discovery), Ribbon (load balancing),
Resilience4j (circuit breaker).
🔁 Possible Follow-up Questions:
➤ How do you implement load balancing with Spring Cloud?
➤ What is Eureka and how does service discovery work?
➤ What is a Circuit Breaker pattern? How do you implement it with Resilience4j?
➤ What is the difference between synchronous and asynchronous communication in
microservices?
➤ What is an API Gateway and why do you need it?
8.2 Circuit Breaker Pattern with Resilience4j
Q17: What is the Circuit Breaker pattern? How do you implement it in Spring Boot?
Answer:
The Circuit Breaker pattern prevents cascading failures in microservices. When a service is
failing, instead of letting requests pile up and fail slowly, the circuit 'opens' and fails fast.
Three States:
• CLOSED — Normal operation. Requests flow through.
• OPEN — Circuit is open. Requests immediately fail (fallback called). No actual calls made.
• HALF-OPEN — Test state after wait duration. Allows some requests. If they succeed →
CLOSED. If fail → OPEN again.
// Add dependency: spring-cloud-starter-circuitbreaker-resilience4j
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
public User getUser(Long id) {
return [Link](id); // might fail
}
public User getUserFallback(Long id, Exception ex) {
[Link]("Fallback for user {}: {}", id, [Link]());
return new User(id, "Unknown", "unknown@[Link]"); // default/cached response
}
# [Link]
[Link]-window-size=10
[Link]-rate-threshold=50
[Link]-duration-in-open-state=5s
[Link]-number-of-calls-in-
half-open-state=3
Other Resilience4j patterns: @Retry, @RateLimiter, @Bulkhead, @TimeLimiter
🔁 Possible Follow-up Questions:
➤ What is the difference between Circuit Breaker and Retry pattern?
➤ What is the Bulkhead pattern?
➤ How do you monitor circuit breaker state?
➤ What was Hystrix and why was it replaced by Resilience4j?
SECTION 9: TESTING IN SPRING BOOT
9.1 Unit Testing & Integration Testing
Q18: Explain @SpringBootTest vs @WebMvcTest vs @DataJpaTest. When do you use
each?
@SpringBootTest — Full Integration Test:
Loads the COMPLETE application context. Use for end-to-end integration tests.
Slow because it starts everything.
@SpringBootTest(webEnvironment = [Link].RANDOM_PORT)
class OrderIntegrationTest {
@Autowired TestRestTemplate restTemplate;
@Test void createOrderTest() {
ResponseEntity<Order> response = [Link]("/api/orders", request,
[Link]);
assertThat([Link]()).isEqualTo([Link]);
}
}
@WebMvcTest — Controller Layer Test (Slice Test):
Loads ONLY web layer (controllers, filters, @ControllerAdvice). Service/Repo beans are mocked.
Fast — no DB or service layer loaded.
@WebMvcTest([Link])
class UserControllerTest {
@Autowired MockMvc mockMvc;
@MockBean UserService userService;
@Test void getUserTest() throws Exception {
when([Link](1L)).thenReturn(new User(1L, "John"));
[Link](get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"));
}
}
@DataJpaTest — Repository Layer Test (Slice Test):
Loads ONLY JPA layer. Uses in-memory H2 DB. Transactions are rolled back after each test.
@DataJpaTest
class UserRepositoryTest {
@Autowired TestEntityManager em;
@Autowired UserRepository userRepo;
@Test void findByEmailTest() {
User user = [Link](new User("test@[Link]", "Test User"));
Optional<User> found = [Link]("test@[Link]");
assertThat(found).isPresent().get().extracting(User::getName).isEqualTo("Test
User");
}
}
🔁 Possible Follow-up Questions:
➤ What is @MockBean vs @Mock?
➤ How do you test @ExceptionHandler in WebMvcTest?
➤ How do you write tests for @Service with Mockito?
➤ What is @Transactional in tests — does it auto-rollback?
➤ How do you use @TestContainers for database integration tests?
SECTION 10: SCENARIO-BASED QUESTIONS
🎯 SCENARIO: Your Spring Boot application is consuming too much memory and GC pauses are
frequent. How do you troubleshoot and fix this?
Approach:
1. Use /actuator/metrics or /actuator/heapdump to gather data
2. Analyze heap dump with Eclipse MAT or VisualVM — look for memory leaks
3. Common causes in Spring Boot:
• Singleton beans holding large collections (check stateful singletons)
• Hibernate L2 cache not configured properly causing excessive caching
• Thread-local variables not cleared
• Too many open DB connections (HikariCP pool misconfigured)
4. Check HikariCP settings: [Link]-pool-size
5. Review Hibernate 2nd level cache configuration
6. Use @Lazy for heavy beans that aren't always needed
7. Tune JVM: -Xmx, -Xms, GC algorithm (-XX:+UseG1GC)
🎯 SCENARIO: Your REST API is returning stale data even though the database has been
updated. What could be causing this?
Likely causes and solutions:
1. Hibernate 1st Level Cache (Session Cache): Within same transaction, JPA caches entities.
Call [Link](entity) or use a new transaction.
2. Spring Cache (@Cacheable): If method is annotated with @Cacheable, old data is returned
from cache. Use @CacheEvict or @CachePut after updates.
3. Hibernate 2nd Level Cache (L2 Cache): Ehcache/Redis caching entities. Configure proper
eviction or use @CacheEvict on update operations.
4. HTTP response caching: Check Cache-Control headers — CDN or browser may be caching
responses.
5. Read replica lag: If using master-slave DB setup, reads go to replica which may lag behind
writes.
Solution depends on root cause. Add cache eviction on write operations and verify transaction
boundaries.
🎯 SCENARIO: You need to migrate a legacy monolith to microservices. How do you approach
this with Spring Boot?
Strangler Fig Pattern Approach:
1. Don't rewrite everything at once. Incrementally extract bounded contexts.
2. Identify domain boundaries (Orders, Users, Payments, Inventory).
3. Set up API Gateway (Spring Cloud Gateway) in front of the monolith.
4. Extract one service at a time: start with the least coupled domain.
5. Use event-driven communication (Kafka/RabbitMQ) to decouple services.
6. Implement Saga pattern for distributed transactions.
7. Set up service discovery (Eureka), centralized config (Spring Cloud Config), distributed tracing
(Zipkin/Sleuth).
8. Use database-per-service pattern — each microservice owns its data.
9. Gradually route traffic to new services via the API gateway.
10. Once a domain is fully extracted, remove it from the monolith.
🎯 SCENARIO: How would you implement rate limiting on your REST APIs in Spring Boot?
Options:
1. Resilience4j @RateLimiter: Declarative rate limiting per method.
@RateLimiter(name = "apiRateLimiter", fallbackMethod = "rateLimitFallback")
@GetMapping("/api/data") public ResponseEntity<Data> getData() { ... }
[Link]-for-period=10
[Link]-refresh-period=1s
[Link]-duration=0s
2. Spring Cloud Gateway with RequestRateLimiter filter (Redis-backed):
• Supports per-user, per-IP rate limiting using token bucket algorithm.
• Requires Redis for distributed rate limiting across multiple instances.
3. Custom Filter: Implement OncePerRequestFilter with an in-memory map or Redis to track
request counts per IP/API key.
SECTION 11: AOP — ASPECT ORIENTED
PROGRAMMING
11.1 AOP Concepts
Q19: Explain AOP in Spring Boot. What are Aspect, Advice, Pointcut, JoinPoint, and
Weaving?
AOP Key Terms:
• Aspect: A class that contains cross-cutting concerns (logging, security, transaction).
Annotated with @Aspect.
• Advice: The action taken at a JoinPoint. Types: @Before, @After, @AfterReturning,
@AfterThrowing, @Around.
• Pointcut: Expression that matches JoinPoints where advice should run. e.g., 'execution(*
[Link].*.*(..))'
• JoinPoint: A specific point in execution (method call, field access). In Spring AOP, only
method execution.
• Weaving: The process of linking Aspects with target objects. Spring uses runtime proxy-
based weaving.
@Aspect
@Component
public class LoggingAspect {
// Pointcut expression: any method in service package
@Pointcut("execution(* [Link]..*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void logBefore(JoinPoint jp) {
[Link]("Calling: {} with args: {}", [Link](), [Link]());
}
@Around("serviceLayer()")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = [Link]();
Object result = [Link](); // actual method call
long time = [Link]() - start;
[Link]("{} executed in {}ms", [Link](), time);
return result;
}
@AfterThrowing(pointcut="serviceLayer()", throwing="ex")
public void logException(JoinPoint jp, Exception ex) {
[Link]("Exception in {} : {}", [Link](), [Link]());
}
}
Common use cases: Logging, performance monitoring, transaction management, security
checks, audit trails.
🔁 Possible Follow-up Questions:
➤ What is the difference between Spring AOP and AspectJ?
➤ Why does AOP not work on self-invocation (same class)?
➤ What is @EnableAspectJAutoProxy?
➤ Can AOP be applied to private methods?
➤ What is the difference between CGLIB proxy and JDK dynamic proxy?
SECTION 12: CACHING IN SPRING BOOT
12.1 Spring Cache Abstraction
Q20: Explain Spring Boot caching. What are @Cacheable, @CacheEvict, and
@CachePut?
Answer:
Spring Cache abstraction decouples cache logic from business logic. You annotate methods —
Spring manages cache population and eviction. Works with any backend: Ehcache, Redis,
Caffeine, ConcurrentHashMap.
@EnableCaching // Add to main class
@Cacheable — Cache method result on first call, return from cache on subsequent
calls:
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findById(Long id) {
return [Link](id).orElse(null); // DB hit only on first call
}
@CachePut — Always execute method AND update cache (good for updates):
@CachePut(value = "users", key = "#[Link]")
public User updateUser(User user) {
return [Link](user);
}
@CacheEvict — Remove from cache (good for deletes):
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) { [Link](id); }
@CacheEvict(value = "users", allEntries = true) // clear entire cache
public void clearAllUserCache() {}
Redis Cache configuration:
[Link]=redis
[Link]=localhost
[Link]=6379
[Link]-to-live=600000 # 10 minutes TTL
🔁 Possible Follow-up Questions:
➤ What is the default cache provider in Spring Boot?
➤ How do you configure TTL (time to live) for cached entries?
➤ What happens if @Cacheable is used on a method in the same class (self-invocation)?
➤ How do you use condition vs unless in @Cacheable?
➤ What is cache stampede / thundering herd problem and how to prevent it?
SECTION 13: QUICK FIRE QUESTIONS
These are short-answer questions commonly asked as rapid-fire in interviews:
Question Answer
What is @Bean vs @Component? @Component is class-level (auto-detected by scan).
@Bean is method-level in @Configuration class
(explicit instantiation, more control).
What is @Primary? When multiple beans of same type exist, @Primary
marks the default one to inject.
What is @Lazy? Bean is instantiated only when first requested, not at
startup.
What is @Async? Runs method in a separate thread from a thread pool.
Requires @EnableAsync on config class. Returns void
or Future/CompletableFuture.
What is @Scheduled? Runs method on a schedule. @Scheduled(cron='0 0 * *
* *') or fixedRate/fixedDelay. Requires
@EnableScheduling.
What is BeanFactory vs BeanFactory is basic DI container (lazy init).
ApplicationContext? ApplicationContext extends it — adds event publishing,
i18n, AOP, eager init of singletons.
What is Spring Boot DevTools? Provides automatic restart on code change, LiveReload
for browser, relaxed config for dev profile.
What is @Transactional readOnly=true? Hibernate skips dirty checking, flush mode set to
NEVER. Gives performance benefit for reads.
What is CommandLineRunner vs Both run after context loads. CommandLineRunner gets
ApplicationRunner? String[] args. ApplicationRunner gets
ApplicationArguments (parsed).
What is [Link]-auto? Controls schema generation: none, validate, update,
create, create-drop. Use validate/none in production.
What is @EventListener? Listens for Spring application events. Can also use
ApplicationEventPublisher to publish custom events.
What is @ConditionalOnProperty? Bean created only if specified property has a certain
value. Useful for feature flags.
What is Flyway/Liquibase? Database migration tools. Flyway uses SQL scripts
versioned as V1__init.sql. Auto-applied on startup.
Better than ddl-auto for production.
What is @ResponseBody? Tells Spring to serialize return value to response body
(JSON/XML), not treat it as a view name.
What is DispatcherServlet? Front controller in Spring MVC. Receives all HTTP
requests and delegates to appropriate handlers
(controllers).
FINAL TIPS FOR ACCENTURE INTERVIEW
Do's:
• Always explain WHY you're using something, not just WHAT it is.
• Use real project examples when possible — 'In my last project, we had...'
• For scenario questions: State the problem → Identify root cause → Give solution →
Mention trade-offs.
• Mention best practices (Constructor injection, @ConfigurationProperties over @Value,
@RestControllerAdvice for exception handling).
• Know the difference between Spring Boot 2.x and Spring Boot 3.x (Jakarta EE 9+, Java 17
baseline, Micrometer for Observability).
Topics to Review Further:
• Spring Batch (job processing)
• Spring Cloud components (Config, Gateway, Eureka, Zipkin)
• Docker + Kubernetes deployment of Spring Boot apps
• Reactive programming with WebFlux (basics)
• Kafka integration with Spring Boot (@KafkaListener, @KafkaHandler)
Remember:
• Accenture interviews focus heavily on real project experience at Level 7-8
• Be ready to code on paper/whiteboard — especially REST endpoint + JPA entity +
Repository
• Good luck! You've got this 🚀
◆ JAVA COLLECTIONS ◆
◆ JAVA OOPs ◆
◆ JAVA EXCEPTION HANDLING ◆
◆ STREAMS • GENERICS • FUNCTIONAL
INTERFACES ◆
◆ JAVA BASICS • MULTITHREADING ◆
JAVA COLLECTIONS
Complete Interview Preparation Guide
Custom Software Engineer | Accenture | 3+ Years Experience Level
Covers: Collection Hierarchy • List • Set • Map • Queue • Iterator • Comparable vs Comparator • Fail-Fast vs Fail-
Safe • Concurrent Collections • Scenario Questions
SECTION 1: COLLECTIONS FRAMEWORK HIERARCHY &
OVERVIEW
Q1: What is the Java Collections Framework? Explain the hierarchy.
Answer:
The Java Collections Framework (JCF) is a unified architecture for storing and manipulating groups of
objects. It provides interfaces, implementations (classes), and algorithms.
Root interfaces:
• Iterable<T> — the very top; allows for-each loop (has iterator() method)
• Collection<T> — extends Iterable; root of most collection types
Collection sub-interfaces:
• List<E> — ordered, allows duplicates, index-based access
– Implementations: ArrayList, LinkedList, Vector, Stack, CopyOnWriteArrayList
• Set<E> — no duplicates allowed
– Implementations: HashSet, LinkedHashSet, TreeSet, CopyOnWriteArraySet
• Queue<E> — FIFO ordering, for holding elements before processing
– Implementations: LinkedList, PriorityQueue, ArrayDeque
• Deque<E> — double-ended queue, insert/remove from both ends
– Implementations: ArrayDeque, LinkedList
Map (NOT part of Collection hierarchy):
• Map<K,V> — key-value pairs, keys are unique
– Implementations: HashMap, LinkedHashMap, TreeMap, Hashtable, ConcurrentHashMap
• SortedMap<K,V> → NavigableMap<K,V> → TreeMap
Key utility classes:
• Collections — static utility methods (sort, shuffle, synchronizedList, unmodifiableList)
• Arrays — array utility methods (asList, sort, binarySearch)
💡 Interview Tip: Draw or describe the hierarchy tree in the interview — it demonstrates strong conceptual
clarity.
🔁 Follow-up: What is the difference between Collection (interface) and Collections (class)?
– Collection is the interface; Collections is a utility class with static helper methods.
SECTION 2: LIST — ArrayList, LinkedList, Vector
Q2: What is the difference between ArrayList and LinkedList? When do you use which?
Answer:
Both implement List<E> but differ fundamentally in their internal data structure and performance
characteristics.
ArrayList:
• Backed by a dynamic array (Object[])
• Default initial capacity: 10. Grows by 50% when full (newCapacity = oldCapacity * 3/2 + 1)
• Random access O(1) — get(index) is constant time
• Add at end: amortized O(1). Add/remove in middle: O(n) — shifts elements
• Better cache locality (contiguous memory) — faster iteration
LinkedList:
• Doubly linked list — each node holds data + pointer to prev/next
• No random access — get(index) is O(n) — traverses from head or tail
• Add/remove at beginning or end: O(1). Add/remove in middle: O(n) to find + O(1) to link
• Also implements Deque<E> — can be used as a queue or stack
• Higher memory overhead (each node = data + 2 pointers)
When to use ArrayList:
• Frequent read/access by index (most common use case)
• Iteration over large lists
• When you know approximate size upfront (use new ArrayList<>(initialCapacity))
When to use LinkedList:
• Frequent insertions/deletions at the beginning or end
• When used as a Queue or Deque
• When memory is not a concern and you rarely access by index
**In practice:** ArrayList is preferred 90% of the time. LinkedList's advantages rarely outweigh its
disadvantages in modern hardware due to cache effects.
🔁 Follow-up: What happens internally when ArrayList runs out of capacity?
– A new array of size (oldCapacity * 3/2 + 1) is allocated and all elements are copied via [Link]()
🔁 Follow-up: How do you make an ArrayList thread-safe?
– [Link](new ArrayList<>()) or use CopyOnWriteArrayList
🔁 Follow-up: What is the time complexity of contains() in ArrayList vs LinkedList?
– Both are O(n) — linear scan (neither is a HashSet)
Q3: What is the difference between ArrayList and Vector? Is Vector still used?
Answer:
Both are dynamic array-based List implementations, but they differ in synchronization and growth strategy.
Synchronization:
• Vector — every method is synchronized (thread-safe but slow due to locking overhead)
• ArrayList — not synchronized (faster; use explicit sync if needed)
Growth factor:
• Vector — doubles its size (100% growth) when full
• ArrayList — grows by 50% (oldCapacity * 3/2 + 1)
Legacy:
• Vector was part of Java 1.0 (before Collections Framework in Java 2)
• It was retrofitted to implement List interface in Java 2
• Stack extends Vector — also legacy
Is Vector still used?
No, Vector is considered legacy/obsolete. Use these alternatives:
• For single-threaded: ArrayList
• For thread-safe with rare writes: CopyOnWriteArrayList
• For thread-safe with frequent reads/writes: [Link](new ArrayList<>())
🔁 Follow-up: What is Stack in Java? Is it recommended?
– Stack extends Vector, also legacy. Use ArrayDeque instead for stack operations (push/pop).
💡 Interview Tip: Saying 'Vector is legacy; I prefer ArrayList or CopyOnWriteArrayList' shows you know modern
Java.
Q4: What are the ways to iterate over a List? What is the difference between Iterator and
ListIterator?
Answer:
5 ways to iterate a List:
// 1. Enhanced for-each (most common)
for (String s : list) { [Link](s); }
// 2. Iterator
Iterator<String> it = [Link]();
while ([Link]()) { String s = [Link](); if ([Link]('remove')) [Link]();
}
// 3. ListIterator (bidirectional)
ListIterator<String> lit = [Link]([Link]()); // start from end
while ([Link]()) { [Link]([Link]()); }
// 4. Traditional for loop with index
for (int i = 0; i < [Link](); i++) { [Link]([Link](i)); }
// 5. Java 8 forEach with lambda
[Link](s -> [Link](s)); // or:
[Link]([Link]::println)
Iterator vs ListIterator:
• Iterator — works on ANY Collection (List, Set, Queue). Traversal: forward only.
Methods: hasNext(), next(), remove()
• ListIterator — works ONLY on List. Bidirectional traversal.
Methods: hasNext(), next(), hasPrevious(), previous(), add(), set(), remove(), nextIndex(), previousIndex()
Why use Iterator instead of for-each when removing?
// WRONG — ConcurrentModificationException!
for (String s : list) { if ([Link]()) [Link](s); }
// CORRECT — use [Link]()
Iterator<String> it = [Link]();
while ([Link]()) { if ([Link]().isEmpty()) [Link](); }
// Java 8 alternative
[Link](String::isEmpty);
🔁 Follow-up: What is ConcurrentModificationException? When does it occur?
– Thrown when a collection is structurally modified while being iterated (except via [Link]()).
Detected via modCount counter.
SECTION 3: SET — HashSet, LinkedHashSet, TreeSet
Q5: Explain HashSet, LinkedHashSet, and TreeSet. When do you use each?
Answer:
All three implement Set<E> (no duplicates), but differ in ordering and performance.
HashSet:
• Backed by a HashMap internally (elements stored as keys, dummy PRESENT value)
• No guaranteed order — iteration order is unpredictable
• O(1) average for add, remove, contains (depends on hashCode quality)
• Allows one null element
• Best performance for general-purpose Set use
Set<String> set = new HashSet<>(); // {banana, apple, cherry} — no order
LinkedHashSet:
• Backed by a LinkedHashMap (HashMap + doubly-linked list)
• Maintains insertion order
• Slightly slower than HashSet due to linked list overhead
• Allows one null element
• Use when you need no-duplicates AND insertion order preserved
Set<String> set = new LinkedHashSet<>(); // {apple, banana, cherry} — insertion
order
TreeSet:
• Backed by a TreeMap (Red-Black Tree)
• Elements are sorted in natural order (or by provided Comparator)
• O(log n) for add, remove, contains
• Does NOT allow null (throws NullPointerException)
• Implements SortedSet and NavigableSet — has extra methods like first(), last(), headSet(), tailSet(),
floor(), ceiling()
Set<String> set = new TreeSet<>(); // {apple, banana, cherry} — sorted
[Link](); // apple [Link](); // cherry
[Link]('banana'); // [apple] — elements BEFORE banana
When to use:
• HashSet — default choice (fastest, order doesn't matter)
• LinkedHashSet — when insertion order matters (e.g., maintaining user selection order)
• TreeSet — when sorted order is needed (e.g., leaderboard, range queries)
🔁 Follow-up: How does HashSet check for duplicates? (Uses hashCode() to find bucket, then equals() to check
equality)
🔁 Follow-up: What happens if you add a mutable object to a HashSet and then modify it?
– The object's hashCode changes, making it 'lost' in the wrong bucket — the Set appears to contain it
but contains() returns false!
Q6: How does HashSet work internally? Explain the role of hashCode() and equals().
Answer:
HashSet is backed by a HashMap where the elements are stored as keys.
Internal add() flow:
1. Call hashCode() on the object to compute hash
2. Apply a supplemental hash function to reduce collisions
3. Compute bucket index: index = hash & (capacity - 1)
4. If bucket is empty: insert the element
5. If bucket has elements (collision): iterate the chain/tree and call equals() on each
6. If equals() returns true for any element: it's a duplicate, do NOT insert
7. If no equals() match: insert (collision chaining)
The hashCode + equals contract (CRITICAL):
• If [Link](b) is true → [Link]() MUST equal [Link]()
• If [Link]() == [Link]() → [Link](b) may be true OR false (collision is OK)
• If you override equals(), you MUST override hashCode() — otherwise collections break!
Example of broken contract:
class User {
String name;
@Override public boolean equals(Object o) { return
[Link](((User)o).name); }
// FORGOT to override hashCode() !!
}
Set<User> set = new HashSet<>();
[Link](new User('Alice'));
[Link](new User('Alice')); // returns FALSE! (different hashCodes →
different buckets)
Java 8+ change — from linked list to tree in buckets:
When a bucket has 8+ entries (TREEIFY_THRESHOLD), the linked list is converted to a Red-Black Tree
for O(log n) lookup within the bucket.
Load factor and resizing:
• Default capacity: 16, load factor: 0.75
• When size > capacity * loadFactor: resize (double capacity + rehash all elements)
🔁 Follow-up: What is the default hashCode() implementation in Java? (Based on memory address in Object
class)
🔁 Follow-up: What is a good hashCode implementation? (Use [Link](field1, field2) or IDE-generated)
🔁 Follow-up: What happens in HashMap when two keys have the same hashCode but are not equal? (Hash
collision — stored in same bucket as linked list or tree)
SECTION 4: MAP — HashMap, LinkedHashMap, TreeMap,
ConcurrentHashMap
Q7: How does HashMap work internally? Explain in detail.
Answer:
HashMap is one of the most asked internal implementation questions. Know this thoroughly.
Internal structure:
HashMap uses an array of Node<K,V> (called 'table' or 'buckets'). Each bucket can hold:
• null (empty)
• A single Node (no collision)
• A linked list of Nodes (collision — Java 7 and Java 8 when < 8 entries in bucket)
• A Red-Black Tree (Java 8+ when bucket has >= 8 entries — TREEIFY_THRESHOLD)
put(key, value) flow:
1. If key is null → stored in bucket[0] (HashMap allows one null key)
2. Compute hash: hash = [Link](), then spread: hash ^ (hash >>> 16)
3. Compute index: index = hash & (n-1) where n = table length
4. If bucket[index] is empty: create new Node and store
5. If bucket[index] has entries: traverse chain, use equals() to find matching key
– If key found: update value, return old value
– If key not found: append new Node to end of chain
6. If bucket size >= 8 AND table size >= 64: treeify bucket to Red-Black Tree
7. If size > threshold (capacity * loadFactor): resize (double + rehash)
get(key) flow:
1. Compute hash and bucket index (same as put)
2. Check first node: compare hash AND equals()
3. If not found, traverse linked list/tree
4. Return value if found, null otherwise
Key defaults:
• Initial capacity: 16 (must be power of 2)
• Load factor: 0.75 (75% full → resize)
• Threshold = capacity * loadFactor = 16 * 0.75 = 12
Why capacity is always a power of 2:
index = hash & (capacity - 1) is faster than hash % capacity (bitwise AND vs division)
Java 8 improvement:
Bucket converts to TreeNode when >= 8 entries: O(n) → O(log n) worst case lookup
🔁 Follow-up: What is the difference between HashMap and Hashtable?
🔁 Follow-up: What happens when two keys have the same hash? (Collision — stored in same bucket)
🔁 Follow-up: Can HashMap have null keys and null values? (Yes — one null key, multiple null values)
🔁 Follow-up: Is HashMap ordered? (No. Use LinkedHashMap for insertion order, TreeMap for sorted order)
💡 Interview Tip: This is THE most asked Java Collections question. Practice explaining it step by step.
Q8: What is the difference between HashMap, LinkedHashMap, TreeMap, and Hashtable?
Feature HashMap LinkedHashMap / TreeMap / Hashtable
Ordering No ordering LinkedHashMap: insertion order TreeMap: sorted
(natural/Comparator) Hashtable: no ordering
Null keys 1 null key allowed LinkedHashMap: 1 null key TreeMap: NO null key
Hashtable: NO null key
Null values Multiple null values OK LinkedHashMap: yes TreeMap: yes Hashtable:
NO null values
Thread safety NOT thread-safe LinkedHashMap: not safe TreeMap: not safe
Hashtable: synchronized (legacy)
Performance O(1) avg LinkedHashMap: O(1) avg TreeMap: O(log n)
Hashtable: O(1) but slow due to sync
Backed by Array of Nodes LinkedHashMap: HashMap + linked list TreeMap:
Red-Black Tree Hashtable: Array (legacy)
Use when Default choice LinkedHashMap: ordered iteration TreeMap:
sorted/range queries Hashtable: never use
(legacy)
Important additional notes:
Iterating a Map:
// Most efficient way — EntrySet iteration
for ([Link]<String, Integer> entry : [Link]()) {
[Link]([Link]() + ' = ' + [Link]());
}
// Java 8 forEach
[Link]((k, v) -> [Link](k + ' = ' + v));
Useful Map methods (Java 8+):
[Link]('key', 0); // return 0 if key missing
[Link]('key', value); // only put if key not present
[Link]('key', k -> new ArrayList<>()); // compute and put if absent
[Link]('key', 1, Integer::sum); // great for frequency counting
[Link]('key', (k, v) -> v == null ? 1 : v + 1); // conditional compute
🔁 Follow-up: How do you sort a HashMap by value?
– [Link]().stream().sorted([Link]()).collect([Link](...))
🔁 Follow-up: How do you make a HashMap thread-safe without using ConcurrentHashMap?
– [Link](new HashMap<>()) — but ConcurrentHashMap is better
Q9: What is ConcurrentHashMap? How is it different from HashMap and synchronized
HashMap?
Answer:
ConcurrentHashMap is a thread-safe, high-performance Map implementation in [Link].
Problems with HashMap in multithreading:
• HashMap is not thread-safe — concurrent puts can corrupt the internal structure
• In Java 7, concurrent resize could create infinite loops in linked lists
Hashtable (legacy) — too coarse-grained:
• Every method synchronized on the whole object → only one thread at a time → poor throughput
[Link] — same problem:
• Wraps every method with synchronized(mutex) → single lock → still one thread at a time
ConcurrentHashMap — how it works:
Java 7 — Segment-based locking:
• Divided into 16 Segments (each is a ReentrantLock)
• Different threads can write to different segments simultaneously
• 16x better throughput than Hashtable
Java 8+ — Node-level locking (CAS + synchronized):
• Uses Compare-And-Swap (CAS) for lock-free operations where possible
• Only locks the individual bucket (Node) when CAS fails
• Much finer granularity — near HashMap performance with thread safety
• size() returns approximate count (uses LongAdder internally)
Key differences:
• ConcurrentHashMap does NOT allow null keys or null values (throws NullPointerException)
– Reason: In concurrent context, get(key)==null is ambiguous (absent vs null value)
• HashMap allows one null key and multiple null values
When to use:
• Read-heavy concurrent access → ConcurrentHashMap
• Write-heavy concurrent access → ConcurrentHashMap or ConcurrentSkipListMap
• Need sorted concurrent Map → ConcurrentSkipListMap
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
[Link]('key', 1);
[Link]('key', k -> expensiveComputation(k));
// Atomic increment
[Link]('word', 1, Integer::sum);
🔁 Follow-up: What is the difference between ConcurrentHashMap and synchronizedMap?
– synchronizedMap: single lock on entire map; ConcurrentHashMap: fine-grained bucket-level locking
via CAS
🔁 Follow-up: Why does ConcurrentHashMap not allow null keys?
🔁 Follow-up: What is CopyOnWriteArrayList and when would you use it over ConcurrentHashMap?
SECTION 5: QUEUE, DEQUE & PRIORITY QUEUE
Q10: Explain Queue and Deque in Java. What is the difference between offer(), add(), poll(),
remove()?
Answer:
Queue represents a FIFO (First-In-First-Out) data structure. Deque (Double-Ended Queue) allows
insertion and removal at both ends.
Queue method pairs (this is a very common question):
• add(e) vs offer(e) — both INSERT at the tail:
– add(): throws IllegalStateException if capacity exceeded
– offer(): returns false if capacity exceeded (preferred for bounded queues)
• remove() vs poll() — both RETRIEVE AND REMOVE from head:
– remove(): throws NoSuchElementException if queue is empty
– poll(): returns null if queue is empty (safer)
• element() vs peek() — both RETRIEVE WITHOUT REMOVING from head:
– element(): throws NoSuchElementException if queue is empty
– peek(): returns null if queue is empty (safer)
Queue<String> queue = new LinkedList<>();
[Link]('A'); [Link]('B'); [Link]('C');
[Link](); // 'A' — does not remove
[Link](); // 'A' — removes and returns
[Link](); // 'B'
Deque — ArrayDeque (preferred Stack/Queue implementation):
Deque<String> deque = new ArrayDeque<>();
[Link]('A'); // add to front
[Link]('B'); // add to back
[Link](); // look at front
[Link](); // remove from back
// ArrayDeque as Stack (faster than Stack class)
Deque<String> stack = new ArrayDeque<>();
[Link]('A'); // same as addFirst()
[Link](); // same as removeFirst()
[Link](); // same as peekFirst()
Why ArrayDeque over LinkedList for Queue/Stack:
• No node overhead (array-based, better cache performance)
• Faster in practice for most operations
• Does NOT allow null elements
🔁 Follow-up: What is the difference between Stack and Deque for stack operations?
– Stack is legacy (extends Vector, synchronized); ArrayDeque is modern and faster
🔁 Follow-up: What is PriorityQueue?
Q11: What is PriorityQueue? How does it work internally?
Answer:
PriorityQueue is a Queue where elements are ordered by priority rather than insertion order. It is backed
by a min-heap (binary heap array).
Key characteristics:
• Elements are ordered by natural ordering (Comparable) or a provided Comparator
• The head of the queue is ALWAYS the smallest element (min-heap by default)
• NOT thread-safe (use PriorityBlockingQueue for concurrent use)
• Does NOT allow null elements
• O(log n) for offer() and poll(); O(1) for peek()
Default (min-heap — smallest element first):
PriorityQueue<Integer> pq = new PriorityQueue<>();
[Link](30); [Link](10); [Link](20);
[Link](); // returns 10 (smallest)
[Link](); // returns 20
Max-heap — largest element first:
PriorityQueue<Integer> maxPq = new PriorityQueue<>([Link]());
// OR: new PriorityQueue<>((a, b) -> b - a);
Custom objects:
PriorityQueue<Task> taskQueue = new PriorityQueue<>(
[Link](Task::getPriority)); // lowest priority number first
Internal structure (binary heap):
• Stored as an array: parent at index i, children at 2i+1 and 2i+2
• offer(): add at end, then sift-up (heapify up) — O(log n)
• poll(): remove root (min), replace with last element, sift-down — O(log n)
• peek(): just return array[0] — O(1)
Common interview use cases:
• Top K elements (use min-heap of size K)
• Dijkstra's shortest path algorithm
• Task scheduling by priority
• Merge K sorted lists
🔁 Follow-up: What is the difference between PriorityQueue and TreeSet?
– Both maintain order, but PriorityQueue allows duplicates; TreeSet does not.
– PriorityQueue only guarantees order at poll(); TreeSet maintains full sorted order.
🔁 Follow-up: How do you convert a max-heap to a min-heap? (Reverse the comparator)
SECTION 6: COMPARABLE vs COMPARATOR
Q12: What is the difference between Comparable and Comparator? When do you use each?
Answer:
Both are used for sorting objects, but they serve different purposes.
Comparable<T> ([Link] package):
• Interface with single method: int compareTo(T other)
• Defines the NATURAL ordering of the class
• The class itself implements it — it's the default sort order
• Used by [Link](), [Link](), TreeSet, TreeMap automatically
public class Employee implements Comparable<Employee> {
private String name;
private int salary;
@Override
public int compareTo(Employee other) {
return [Link]([Link], [Link]); // sort by salary
ascending
}
}
[Link](employees); // uses compareTo automatically
Comparator<T> ([Link] package):
• Interface with single method: int compare(T o1, T o2)
• Defines EXTERNAL / CUSTOM ordering — separate from the class
• Allows multiple different sort orders for the same class
• Passed as a parameter to sort methods
// Sort by name
Comparator<Employee> byName = [Link](Employee::getName);
// Sort by salary descending
Comparator<Employee> bySalaryDesc =
[Link](Employee::getSalary).reversed();
// Chained: sort by dept, then by salary within dept
Comparator<Employee> complex = [Link](Employee::getDept)
.thenComparingInt(Employee::getSalary)
.thenComparing(Employee::getName);
[Link](bySalaryDesc);
[Link]([Link](Employee::getName));
Return value convention for compareTo / compare:
• Negative → first object is LESS THAN second (comes before)
• Zero → they are EQUAL
• Positive → first object is GREATER THAN second (comes after)
When to use which:
• Comparable — when the class has one obvious natural order (e.g., Integer, String, LocalDate all
implement Comparable)
• Comparator — when you need multiple sort orders, sorting third-party classes, or dynamic sort
criteria
🔁 Follow-up: What is the contract of compareTo()? (Must be consistent with equals: [Link](b)==0
should imply [Link](b))
🔁 Follow-up: What does [Link]() and [Link]() do?
🔁 Follow-up: How do you sort a list of strings case-insensitively?
[Link](String.CASE_INSENSITIVE_ORDER); // or
[Link](String::toLowerCase)
SECTION 7: FAIL-FAST vs FAIL-SAFE ITERATORS
Q13: What is the difference between Fail-Fast and Fail-Safe iterators?
Answer:
This distinction is about how iterators behave when the collection is modified during iteration.
Fail-Fast Iterator:
• Immediately throws ConcurrentModificationException if the collection is structurally modified
(add/remove) during iteration — except through the iterator's own remove() method
• Uses an internal modCount counter: iterator checks modCount at each next() call
• Most [Link] collections: ArrayList, HashSet, HashMap, LinkedList, TreeMap etc.
List<String> list = new ArrayList<>([Link]('a','b','c'));
for (String s : list) {
[Link]('d'); // throws ConcurrentModificationException!
}
// Safe removal — use [Link]()
Iterator<String> it = [Link]();
while ([Link]()) { if ([Link]().equals('a')) [Link](); } // OK
Fail-Safe Iterator:
• Does NOT throw ConcurrentModificationException
• Works on a CLONE/COPY of the collection — modifications to original don't affect ongoing iteration
• [Link] collections: CopyOnWriteArrayList, ConcurrentHashMap, CopyOnWriteArraySet
CopyOnWriteArrayList<String> cowList = new
CopyOnWriteArrayList<>([Link]('a','b','c'));
for (String s : cowList) {
[Link]('d'); // NO exception — iterates over the original snapshot
}
// After loop, cowList has: [a, b, c, d, d, d] — 'd' added 3 times
Trade-offs of Fail-Safe:
• Memory overhead — copying the backing array on every write
• Stale data — iterator may not see latest modifications
• Best for: read-heavy, rarely-modified collections in concurrent environments
Summary table:
Fail-Fast Fail-Safe
Throws ConcurrentModificationException Never throws ConcurrentModificationException
Works on original collection Works on a copy/snapshot
No extra memory overhead Higher memory usage
ArrayList, HashSet, HashMap, LinkedList CopyOnWriteArrayList, ConcurrentHashMap
Reflects latest changes during iteration May see stale data
Use in single-threaded or when no concurrent Use in concurrent read-heavy scenarios
mod
🔁 Follow-up: What is modCount? How does it work?
– Every structural modification (add/remove/clear) increments modCount. Iterator captures modCount at
creation. Each next() call checks if current modCount == captured modCount. If not, throws
ConcurrentModificationException.
🔁 Follow-up: Is fail-fast guaranteed to always throw? (No — the spec says 'best-effort' — not guaranteed for all
cases)
🔁 Follow-up: Can you use CopyOnWriteArrayList for frequent writes? (No — O(n) for every write; meant for
rare-write scenarios)
SECTION 8: IMMUTABLE & UNMODIFIABLE COLLECTIONS
Q14: What is the difference between [Link]() and [Link]()? What about
[Link]()?
Answer:
These all create 'read-only-ish' lists but with important differences.
[Link]() — Fixed-size, mutable elements:
List<String> list = [Link]('a', 'b', 'c');
• Fixed size — add() and remove() throw UnsupportedOperationException
• MUTABLE elements — set() works! You can change existing values
• Backed by the original array — changes to the list affect the array and vice versa
• Allows null values
[Link]() — View wrapper:
List<String> mutable = new ArrayList<>([Link]('a','b','c'));
List<String> readOnly = [Link](mutable);
• All mutation methods (add, remove, set, clear) throw UnsupportedOperationException
• But it's a VIEW — if the original mutable list changes, the read-only view reflects the change!
• Allows null values
[Link]() (Java 9+) — Truly immutable:
List<String> immutable = [Link]('a', 'b', 'c');
• Truly immutable — add, remove, set ALL throw UnsupportedOperationException
• NOT backed by any mutable structure — completely independent
• Does NOT allow null values (throws NullPointerException)
• Compact internal representation (optimized for 0, 1, 2 elements)
**[Link]() and [Link]() — same rules as [Link]():
Map<String, Integer> map = [Link]('a', 1, 'b', 2); // immutable
Set<String> set = [Link]('a', 'b', 'c'); // immutable
[Link]() / [Link]() / [Link]() (Java 10+):
List<String> copy = [Link](existingList); // immutable copy
🔁 Follow-up: What happens when you try to add to a [Link]() list?
– Throws UnsupportedOperationException immediately
🔁 Follow-up: Why avoid null in [Link]()? (Consistency — null often indicates bugs in immutable collections)
🔁 Follow-up: How do you create an immutable Map with more than 10 entries?
([Link]([Link](k,v), ...))
SECTION 9: COLLECTIONS UTILITY CLASS & IMPORTANT
METHODS
Q15: What are the important methods in the Collections utility class?
Answer:
Collections (note the 's') is a utility class with static methods for operating on Collection objects.
Sorting & Searching:
[Link](list); // natural order sort
[Link](list, comparator); // custom comparator sort
[Link](list, key); // O(log n) — list must be
sorted!
[Link](list); // reverse order
[Link](list); // random shuffle
[Link](list, new Random(seed)); // deterministic shuffle
Min / Max:
[Link](list); // natural order minimum
[Link](list, comparator); // custom comparator maximum
Frequency & Disjoint:
[Link](list, element); // count occurrences
[Link](list1, list2); // true if no common elements
Bulk Operations:
[Link](list, 'x'); // replace all with 'x'
[Link](dest, src); // dest must have same or larger size
[Link](5, 'hello'); // [hello, hello, hello, hello, hello]
[Link](list, 'old', 'new'); // replace matching elements
[Link](list, i, j); // swap elements at index i and j
[Link](list, distance); // rotate elements
Thread-safe wrappers:
List<String> synchList = [Link](new ArrayList<>());
Set<String> synchSet = [Link](new HashSet<>());
Map<K,V> synchMap = [Link](new HashMap<>());
Immutable wrappers:
List<String> unmodList = [Link](list);
Set<String> unmodSet = [Link](set);
Map<K,V> unmodMap = [Link](map);
Special collections:
[Link](); // immutable empty list — better than new
ArrayList<>() for return values
[Link]('only'); // immutable single-element set
[Link]('only'); // immutable single-element list
🔁 Follow-up: What is the difference between [Link]() and [Link]()? ([Link]() is instance method
added in Java 8, both use TimSort internally)
🔁 Follow-up: What sorting algorithm does [Link]() use? (TimSort — hybrid merge sort + insertion sort
— O(n log n) worst case)
SECTION 10: CONCURRENT COLLECTIONS
Q16: What concurrent collection classes are available in Java? When do you use each?
Answer:
[Link] provides thread-safe alternatives to regular collections:
ConcurrentHashMap:
• Thread-safe HashMap with fine-grained locking (CAS + bucket-level locks)
• Best for concurrent read/write of key-value pairs
• No null keys or values
CopyOnWriteArrayList:
• Thread-safe ArrayList — writes create a new copy of the array
• All reads are lock-free (snapshot isolation)
• O(n) write cost — best when reads >> writes (e.g., event listener lists)
• Fail-safe iterator (works on snapshot)
CopyOnWriteArraySet:
• Backed by CopyOnWriteArrayList — same write characteristics
• Thread-safe Set, fail-safe, best for small, rarely-modified sets
ConcurrentLinkedQueue:
• Lock-free, thread-safe Queue using CAS operations
• Unbounded, non-blocking — best for producer-consumer without capacity constraints
BlockingQueue implementations:
• ArrayBlockingQueue — bounded, backed by array, FIFO, blocks on full/empty
• LinkedBlockingQueue — optionally bounded, backed by linked nodes
• PriorityBlockingQueue — unbounded priority queue (thread-safe PriorityQueue)
• SynchronousQueue — zero-capacity; each put() must wait for a take()
BlockingQueue<Task> queue = new ArrayBlockingQueue<>(100);
[Link](task); // blocks if full
[Link](); // blocks if empty
Best for: ThreadPoolExecutor internally uses LinkedBlockingQueue or SynchronousQueue
ConcurrentSkipListMap / ConcurrentSkipListSet:
• Thread-safe sorted map/set (concurrent alternative to TreeMap/TreeSet)
• O(log n) operations, lock-free using skip list data structure
🔁 Follow-up: What is the difference between ConcurrentLinkedQueue and LinkedBlockingQueue?
– ConcurrentLinkedQueue: non-blocking (never waits); LinkedBlockingQueue: blocking (waits on
put/take)
🔁 Follow-up: What is a BlockingQueue used for in practice?
– Classic producer-consumer pattern; used internally by ExecutorService thread pools
SECTION 11: SCENARIO-BASED & CODING QUESTIONS
Q17: SCENARIO: Find the first non-repeating character in a string using Collections.
Answer:
Classic question testing LinkedHashMap knowledge — preserving insertion order while tracking
frequency.
public static Character firstNonRepeating(String str) {
// LinkedHashMap preserves insertion order
Map<Character, Integer> freq = new LinkedHashMap<>();
for (char c : [Link]()) {
[Link](c, 1, Integer::sum); // [Link](c, [Link](c,0) +
1);
}
for ([Link]<Character, Integer> entry : [Link]()) {
if ([Link]() == 1) return [Link]();
}
return null;
}
// firstNonRepeating('aabbcde') → 'c'
Why LinkedHashMap? We need frequency count (Map) AND original order preserved to find the FIRST
non-repeating. HashMap would not guarantee order.
🔁 Follow-up: What if you used HashMap instead of LinkedHashMap here?
– You'd get the wrong answer because HashMap doesn't preserve insertion order.
🔁 Follow-up: Can you solve it in a single pass? (No — need two passes: one to count, one to find first with
count=1)
💡 Interview Tip: Always explain WHY you chose that data structure — that's what interviews test.
Q18: SCENARIO: Find top K frequent elements from a list of integers.
Answer:
Tests HashMap + PriorityQueue knowledge.
public static List<Integer> topKFrequent(int[] nums, int k) {
// Step 1: Count frequencies
Map<Integer, Integer> freq = new HashMap<>();
for (int n : nums) [Link](n, 1, Integer::sum);
// Step 2: Min-heap of size k (keeps top k frequent)
PriorityQueue<Integer> minHeap =
new PriorityQueue<>([Link](freq::get));
for (int num : [Link]()) {
[Link](num);
if ([Link]() > k) [Link](); // remove least frequent
}
return new ArrayList<>(minHeap);
}
// topKFrequent([1,1,1,2,2,3], 2) → [1, 2]
Time complexity: O(n log k) — n to count, log k for heap ops
Space complexity: O(n) for frequency map
🔁 Follow-up: What if k == array length? (Just sort by frequency — O(n log n))
🔁 Follow-up: Alternative using bucket sort: O(n) solution?
Q19: SCENARIO: Group anagrams together from a list of strings.
Answer:
Tests HashMap with sorted key technique.
public static Map<String, List<String>> groupAnagrams(String[] words) {
Map<String, List<String>> result = new HashMap<>();
for (String word : words) {
char[] chars = [Link]();
[Link](chars);
String key = new String(chars); // sorted chars = anagram signature
[Link](key, k -> new ArrayList<>()).add(word);
}
return result;
}
// Input: ['eat','tea','tan','ate','nat','bat']
// Output: {aet=[eat,tea,ate], ant=[tan,nat], abt=[bat]}
Key insight: All anagrams of a word produce the same sorted string → use as HashMap key
🔁 Follow-up: What is computeIfAbsent()? Why use it here?
– computeIfAbsent(key, mappingFn): if key absent, compute value using fn and put it. Avoids explicit null
check.
🔁 Follow-up: What is the time complexity? (O(n * m log m) where n=words, m=avg word length)
Q20: SCENARIO: Count word frequency from a sentence and print top 3 words.
Answer:
Tests HashMap + sorting by value knowledge.
public static void top3Words(String sentence) {
Map<String, Long> freq = [Link]([Link]().split('\\s+'))
.collect([Link]([Link](),
[Link]()));
[Link]().stream()
.sorted([Link].<String, Long>comparingByValue().reversed())
.limit(3)
.forEach(e -> [Link]([Link]() + ': ' + [Link]()));
}
// Without streams (traditional):
Map<String, Integer> freq = new HashMap<>();
for (String word : [Link](' ')) {
[Link]([Link](), 1, Integer::sum);
}
List<[Link]<String,Integer>> entries = new ArrayList<>([Link]());
[Link]([Link].<String,Integer>comparingByValue().reversed());
[Link](0, [Link](3, [Link]())).forEach([Link]::println);
🔁 Follow-up: How do you sort a Map by value? (Convert entrySet to List, sort with comparator)
🔁 Follow-up: Why can't you sort a HashMap directly? (HashMap has no defined order — must extract entries)
Q21: SCENARIO: Two Sum — find if any two numbers in a list sum to a target value.
Answer:
Classic HashSet/HashMap usage for O(n) lookup.
// Using HashSet — O(n) time, O(n) space
public static boolean twoSum(int[] nums, int target) {
Set<Integer> seen = new HashSet<>();
for (int num : nums) {
if ([Link](target - num)) return true;
[Link](num);
}
return false;
}
// Return the actual pair indices — use HashMap
public static int[] twoSumIndices(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>(); // value → index
for (int i = 0; i < [Link]; i++) {
int complement = target - nums[i];
if ([Link](complement)) {
return new int[]{[Link](complement), i};
}
[Link](nums[i], i);
}
return new int[]{};
}
Key insight: Instead of O(n²) nested loops, use HashSet/HashMap for O(1) lookup of complement.
🔁 Follow-up: What if the array is sorted? (Two-pointer approach — O(n) time, O(1) space — no extra collection
needed)
💡 Interview Tip: Shows understanding of when to trade space for time — fundamental CS principle.
Q22: SCENARIO: Design a cache with LRU (Least Recently Used) eviction using Java
Collections.
Answer:
Classic LinkedHashMap use case — it can be configured as an LRU cache.
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
// accessOrder=true: moves accessed entry to end (most recently used at
tail)
super(capacity, 0.75f, true);
[Link] = capacity;
}
@Override
protected boolean removeEldestEntry([Link]<K, V> eldest) {
return size() > capacity; // evict oldest (head) when over capacity
}
}
LRUCache<Integer, String> cache = new LRUCache<>(3);
[Link](1, 'A'); [Link](2, 'B'); [Link](3, 'C');
[Link](1); // access 1 — moves to end: [2, 3, 1]
[Link](4, 'D'); // capacity exceeded — evicts LRU (2): [3, 1, 4]
How LinkedHashMap supports LRU:
• accessOrder=true: every get() or put() moves the entry to the tail of the internal doubly-linked list
• removeEldestEntry(): called after every put() — override to define eviction policy
• Head of linked list = Least Recently Used → evict when over capacity
🔁 Follow-up: Why does LinkedHashMap work as LRU? (Maintains doubly-linked list of entries in access order)
🔁 Follow-up: What is the time complexity of get/put in this LRU cache? (O(1) — HashMap lookup + linked list
pointer update)
💡 Interview Tip: LinkedHashMap + removeEldestEntry is the standard Java interview answer for LRU cache.
SECTION 12: MASTER COMPARISON TABLES
Complete Collection Comparison
Collection Order Duplicates Null Thread-Safe Time Complexity
ArrayList Insertion Yes Yes No get O(1), add O(1)*
LinkedList Insertion Yes Yes No get O(n), add O(1)
HashSet None No 1 No add/contains O(1)
LinkedHashSet Insertion No 1 No add/contains O(1)
TreeSet Sorted No No No add/contains O(log n)
HashMap None No 1 key No get/put O(1)
LinkedHashMap Insertion No 1 key No get/put O(1)
TreeMap Sorted No No No get/put O(log n)
ConcurrentHashMap None No No Yes get/put O(1)
CopyOnWriteArrayLis Insertion Yes Yes Yes read O(1), write O(n)
t
PriorityQueue Priority Yes No No offer/poll O(log n)
ArrayDeque FIFO/LIFO Yes No No push/pop O(1)
Best of luck with your Accenture Interview! 🚀
Next up: Java Streams | OOPs | Exception Handling
JAVA OOPs
Object-Oriented Programming — Complete Interview Preparation Guide
Custom Software Engineer | Accenture | 3+ Years Experience Level
Covers: 4 Pillars • Class & Object • Constructors • this & super • Inheritance • Polymorphism • Abstraction •
Interfaces • Encapsulation • static & final • Object class methods • Design Patterns • Scenario Questions
SECTION 1: THE FOUR PILLARS OF OOP
Q1: What are the four pillars of OOP? Explain each with a real-world example.
Answer:
The four pillars are the foundation of Object-Oriented Programming. Every OOP interview starts here.
1. Encapsulation — 'Bundling data + behavior, hiding internal details'
Wrapping data (fields) and methods that operate on that data into a single unit (class), and restricting
direct access to internal state via access modifiers.
Real-world: A car's engine. You use the accelerator (public method) without knowing internal combustion
mechanics (private fields).
public class BankAccount {
private double balance; // hidden — cannot access directly
public void deposit(double amount) {
if (amount > 0) balance += amount; // controlled access
}
public double getBalance() { return balance; } // read-only access
}
2. Inheritance — 'Reusing code via parent-child relationship'
A child class acquires properties and behaviours of a parent class using 'extends'. Promotes code reuse
and establishes IS-A relationships.
Real-world: A Dog IS-A Animal. Dog inherits eat(), sleep() from Animal but also has bark().
class Animal { void eat() { [Link]('eating'); } }
class Dog extends Animal { void bark() { [Link]('woof'); } }
Dog d = new Dog(); [Link](); [Link](); // inherited + own method
3. Polymorphism — 'One interface, many forms'
The ability of an object to take many forms. Same method name, different behaviours.
Real-world: A 'Shape' with draw() — Circle draws a circle, Rectangle draws a rectangle.
• Compile-time (Method Overloading): same name, different parameters
• Runtime (Method Overriding): child redefines parent's method
class Shape { void draw() { [Link]('drawing shape'); } }
class Circle extends Shape { @Override void draw() { [Link]('drawing
circle'); } }
Shape s = new Circle(); [Link](); // prints 'drawing circle' — runtime
polymorphism
4. Abstraction — 'Hiding implementation, showing only essentials'
Hiding complex implementation details and exposing only what is necessary. Achieved via abstract
classes and interfaces.
Real-world: A TV remote. You press 'volume up' without knowing the infrared/signal internals.
abstract class PaymentGateway {
abstract void processPayment(double amount); // WHAT to do — not HOW
void generateReceipt() { [Link]('Receipt generated'); } //
common behaviour
}
class StripePayment extends PaymentGateway {
@Override void processPayment(double amount) { /* Stripe API logic */ }
}
🔁 Follow-up: What is the difference between Abstraction and Encapsulation?
– Abstraction: hides WHAT/complexity at design level (what an object does)
– Encapsulation: hides HOW/implementation at class level (how it does it — data hiding)
– Abstraction is achieved via abstract class/interface; Encapsulation via private + getters/setters
💡 Interview Tip: Give a real-world analogy for each pillar — interviewers love concrete examples.
SECTION 2: CLASS, OBJECT & CONSTRUCTORS
Q2: What is the difference between a Class and an Object?
Answer:
• Class — A blueprint or template that defines the structure and behaviour (fields + methods). It does
NOT occupy memory (except for static members).
• Object — A specific instance of a class created using 'new'. It IS allocated memory on the heap.
// Class — blueprint
class Car {
String brand;
int speed;
void accelerate() { speed += 10; }
}
// Object — actual instance in memory
Car myCar = new Car(); // object 1 — own copy of brand, speed
Car yourCar = new Car(); // object 2 — separate copy of brand, speed
Memory model:
• myCar and yourCar are references stored on the Stack
• The actual Car objects (data) are stored on the Heap
• [Link] and [Link] are independent — changes to one don't affect the other
🔁 Follow-up: How many objects are created here: String s = new String('hello');?
– Up to 2: one in the String Pool (if not present) and one in Heap via 'new'
🔁 Follow-up: Where are objects stored in Java? (Heap — objects; Stack — references/primitives/local
variables)
Q3: Explain Constructors in Java. What are the types? What is Constructor Chaining?
Answer:
A constructor is a special method used to initialize an object. It has the same name as the class and no
return type (not even void).
Types of Constructors:
1. Default Constructor (no-arg):
class Employee {
String name;
int id;
Employee() { // default constructor
[Link] = 'Unknown';
[Link] = 0;
}
}
If you define NO constructor, Java provides a default one automatically. As soon as you define ANY
constructor, Java stops providing the default.
2. Parameterized Constructor:
Employee(String name, int id) {
[Link] = name;
[Link] = id;
}
3. Copy Constructor (Java doesn't provide it — must write manually):
Employee(Employee other) { // copy constructor
[Link] = [Link];
[Link] = [Link];
}
Constructor Chaining — this() and super():
Constructor chaining calls one constructor from another in the same or parent class.
class Employee {
String name; int id; String dept;
Employee(String name) {
this(name, 0); // calls Employee(String, int) — MUST be first statement
}
Employee(String name, int id) {
this(name, id, 'General'); // calls Employee(String, int, String)
}
Employee(String name, int id, String dept) {
[Link] = name; [Link] = id; [Link] = dept; // actual
initialization
}
}
Rules for this() and super():
• this() and super() must be the FIRST statement in a constructor
• They cannot both appear in the same constructor
• super() is automatically inserted by compiler if you don't write it (calls parent no-arg constructor)
🔁 Follow-up: What happens if a parent class has no default constructor and child doesn't call super()?
– Compile error! If parent has only parameterized constructors, child MUST explicitly call super(args)
🔁 Follow-up: Can a constructor be private? When would you use it?
– Yes! Used in Singleton pattern and factory methods to prevent direct instantiation
🔁 Follow-up: Can constructors be inherited? (No — constructors are not inherited. Child must define its own)
🔁 Follow-up: What is the difference between a constructor and a method?
– Constructor: no return type, same name as class, auto-called on object creation, not inherited
– Method: has return type, any name, called explicitly, can be inherited and overridden
Q4: Explain the 'this' keyword in Java. What are all its uses?
Answer:
'this' is a reference to the current instance of the class. It has 5 main uses:
1. Resolve naming conflict between instance variable and parameter:
class Person {
String name;
Person(String name) {
[Link] = name; // [Link] = instance variable; name = parameter
}
}
2. Call another constructor in the same class (constructor chaining):
Person() { this('Unknown'); } // delegates to Person(String name)
3. Pass current object as argument to a method:
void register() { [Link](this); } // pass current object
4. Return the current object (Builder pattern / method chaining):
public Builder setName(String name) { [Link] = name; return this; }
new Builder().setName('John').setAge(25).build(); // method chaining
5. Refer to current object explicitly (rare — to distinguish this from outer class in nested class):
class Outer { class Inner { void print() { [Link]([Link]); } } }
🔁 Follow-up: Can 'this' be used inside a static method? (NO — static methods have no instance, so 'this'
doesn't exist there)
SECTION 3: INHERITANCE — extends, super, Method Hiding
Q5: Explain Inheritance in Java. What are the types? Why is multiple inheritance not
supported?
Answer:
Inheritance allows a child class to acquire properties and behaviours of a parent class using 'extends'. It
promotes code reuse and establishes IS-A relationships.
Types of Inheritance in Java:
• Single Inheritance — One class extends one class: Dog extends Animal
• Multilevel Inheritance — Chain: GrandChild extends Child extends Parent
• Hierarchical Inheritance — Multiple children from one parent: Dog extends Animal, Cat extends
Animal
• Multiple Inheritance — One child from multiple parents: NOT SUPPORTED for classes (only for
interfaces)
• Hybrid Inheritance — Combination of above: NOT directly supported for classes
Why Multiple Inheritance is NOT supported for classes — The Diamond Problem:
class A { void show() { [Link]('A'); } }
class B extends A { @Override void show() { [Link]('B'); } }
class C extends A { @Override void show() { [Link]('C'); } }
// Hypothetical: class D extends B, C <-- COMPILE ERROR in Java
// Problem: [Link]() → call B's or C's version? Ambiguity!
This is called the Diamond Problem. Java avoids it by not allowing multiple class inheritance.
Solution: Multiple Inheritance via Interfaces:
Java allows a class to implement multiple interfaces, and with Java 8 default methods:
interface B { default void show() { [Link]('B'); } }
interface C { default void show() { [Link]('C'); } }
class D implements B, C {
@Override public void show() { [Link](); } // MUST override to resolve
ambiguity
}
What IS inherited and what is NOT:
• Inherited: public and protected fields, methods, nested classes
• NOT inherited: private members (accessible only within parent class), constructors, static members
(not truly 'inherited' — accessible via class name)
🔁 Follow-up: What is the difference between IS-A and HAS-A relationships?
– IS-A: Inheritance (Dog IS-A Animal — use extends)
– HAS-A: Composition (Car HAS-A Engine — use as field)
– Prefer composition over inheritance when the relationship is not truly IS-A
🔁 Follow-up: Can you extend a final class? (No — final class cannot be extended, e.g., String, Integer)
🔁 Follow-up: What is the root class of all Java classes? ([Link] — every class implicitly extends
Object)
Q6: What is the 'super' keyword? Explain all its uses.
Answer:
'super' refers to the immediate parent class. It has three uses:
1. Call parent class constructor — super():
class Animal {
String name;
Animal(String name) { [Link] = name; }
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // MUST be first statement — calls Animal(String)
[Link] = breed;
}
}
2. Access parent class field (when hidden by child field):
class Animal { String type = 'Animal'; }
class Dog extends Animal {
String type = 'Dog'; // hides parent field
void printTypes() {
[Link](type); // Dog
[Link]([Link]); // Animal
}
}
3. Call parent class method (when overridden by child):
class Animal { void sound() { [Link]('generic sound'); } }
class Dog extends Animal {
@Override void sound() {
[Link](); // calls [Link]() first
[Link]('woof');
}
}
🔁 Follow-up: Can super() and this() both be used in the same constructor?
– NO. Both must be the first statement, so only one can exist per constructor.
🔁 Follow-up: Can you call [Link]()? (No — Java doesn't allow skipping levels)
Q7: What is Method Hiding vs Method Overriding? What is the difference?
Answer:
This is a tricky and commonly tested distinction.
Method Overriding (instance methods):
• Child class provides its own implementation of a parent's instance method
• Resolved at RUNTIME based on the actual object type (dynamic dispatch)
• @Override annotation is recommended
class Animal { void sound() { [Link]('generic'); } }
class Cat extends Animal { @Override void sound() { [Link]('meow'); }
}
Animal a = new Cat();
[Link](); // 'meow' — RUNTIME decision based on actual object (Cat)
Method Hiding (static methods):
• Child class defines a static method with the same signature as parent's static method
• Resolved at COMPILE TIME based on the reference type (NOT dynamic dispatch)
class Animal { static void describe() { [Link]('Animal'); } }
class Cat extends Animal { static void describe()
{ [Link]('Cat'); } }
Animal a = new Cat();
[Link](); // 'Animal' — COMPILE TIME decision based on reference type
(Animal)
Cat c = new Cat();
[Link](); // 'Cat' — reference type is Cat
Key differences:
• Overriding: instance methods, runtime polymorphism, @Override valid, virtual dispatch
• Hiding: static methods, compile-time resolution, @Override NOT valid for static
💡 Interview Tip: Method hiding is a subtle concept — knowing it separates good candidates from average
ones.
🔁 Follow-up: Can you override a static method? (No — you can only HIDE it. @Override on static gives
compile error)
SECTION 4: POLYMORPHISM — Overloading, Overriding &
Dynamic Dispatch
Q8: What is the difference between Method Overloading and Method Overriding?
Method Overloading Method Overriding
Same class (or child class with same name) Parent + Child class
Same method name, DIFFERENT parameters Same method name, SAME parameters
Compile-time (static) polymorphism Runtime (dynamic) polymorphism
Return type CAN be different Return type must be same or covariant
Access modifier: any Access modifier: cannot be MORE restrictive
Cannot override — resolved by compiler @Override annotation recommended
Static/instance methods both allowed Only instance methods (not static, not private, not final)
Example: print(int), print(String) Example: child overrides parent's draw()
Overloading example:
class Calculator {
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; } // different param types
int add(int a, int b, int c) { return a + b + c; } // different param count
}
Overriding example:
class Vehicle { String getType() { return 'Vehicle'; } }
class Truck extends Vehicle {
@Override String getType() { return 'Truck'; } // same signature
}
Vehicle v = new Truck(); [Link](); // returns 'Truck' — runtime decision
Overriding rules — what CAN'T be overridden:
• private methods — not visible to child
• static methods — hidden, not overridden
• final methods — locked from override
• constructors — not inherited, not overridden
Covariant return type (Java 5+):
class Animal { Animal create() { return new Animal(); } }
class Dog extends Animal {
@Override Dog create() { return new Dog(); } // Dog is subtype of Animal —
valid!
}
🔁 Follow-up: Can you overload the main method? (Yes — but JVM only calls main(String[] args))
🔁 Follow-up: What is covariant return type in overriding?
🔁 Follow-up: What happens if you change only the return type — is it overloading? (No! Compile error — can't
distinguish by return type alone)
Q9: What is Runtime Polymorphism? How does Java implement it internally (vtable)?
Answer:
Runtime polymorphism (dynamic method dispatch) is when the method call is resolved at runtime based
on the actual object type, not the reference type.
Example:
class Shape {
void draw() { [Link]('Drawing Shape'); }
}
class Circle extends Shape {
@Override void draw() { [Link]('Drawing Circle'); }
}
class Rectangle extends Shape {
@Override void draw() { [Link]('Drawing Rectangle'); }
}
Shape[] shapes = { new Circle(), new Rectangle(), new Shape() };
for (Shape s : shapes) {
[Link](); // resolved at runtime based on actual type
}
// Output: Drawing Circle / Drawing Rectangle / Drawing Shape
How JVM implements it — Virtual Method Table (vtable):
• Every class has a vtable — an array of pointers to its method implementations
• When a method is overridden, the child's vtable entry points to the child's version
• When you call [Link]() on a Shape reference, JVM looks up the vtable of the ACTUAL object
• This lookup happens at runtime — hence 'dynamic' dispatch
Upcasting and Downcasting:
Shape s = new Circle(); // upcasting — implicit, always safe
Circle c = (Circle) s; // downcasting — explicit, may throw
ClassCastException
if (s instanceof Circle) { Circle c2 = (Circle) s; } // safe check before
downcast
// Java 16+ pattern matching instanceof
if (s instanceof Circle c3) { [Link](); } // no explicit cast needed
🔁 Follow-up: What is upcasting and downcasting?
🔁 Follow-up: What exception is thrown on invalid downcast? (ClassCastException)
🔁 Follow-up: What is the instanceof operator and when do you use it?
💡 Interview Tip: Explaining vtable shows depth of JVM knowledge — interviewers at senior level appreciate
this.
SECTION 5: ABSTRACT CLASSES
Q10: What is an abstract class? What are the rules? When do you use it?
Answer:
An abstract class is a class declared with the 'abstract' keyword that cannot be instantiated directly. It can
have both abstract methods (no body) and concrete methods (with body).
Rules:
• Declared with 'abstract' keyword
• Cannot be instantiated directly (new AbstractClass() — compile error)
• Can have abstract methods (must be implemented by non-abstract subclasses)
• Can have constructors (called via super() from child)
• Can have instance variables, static methods, concrete methods
• A class with any abstract method MUST be abstract
• A subclass MUST implement ALL abstract methods or be abstract itself
abstract class Animal {
String name; // instance variable
Animal(String name) { [Link] = name; } // constructor
abstract void makeSound(); // abstract — no body, MUST be overridden
void breathe() { [Link](name + ' is breathing'); } // concrete
static void kingdom() { [Link]('Animalia'); } // static
}
class Dog extends Animal {
Dog(String name) { super(name); }
@Override void makeSound() { [Link]('Woof!'); } // MUST
implement
}
// Animal a = new Animal(); // COMPILE ERROR
Animal a = new Dog('Rex'); // OK — upcasting
[Link](); // Woof!
When to use Abstract Class (vs Interface):
• When you want to provide PARTIAL implementation (some methods have default behaviour)
• When subclasses SHARE common state (instance variables — interfaces can't have non-static
state)
• When you want constructors in the base type
• When classes share IS-A relationship with common code (Template Method pattern)
🔁 Follow-up: Can an abstract class have no abstract methods? (Yes! You can declare a class abstract just to
prevent instantiation)
🔁 Follow-up: Can an abstract class implement an interface without implementing all its methods? (Yes — the
abstract class inherits the obligation; concrete subclass must implement them)
🔁 Follow-up: What is the Template Method design pattern?
– Abstract class defines the algorithm skeleton (concrete methods); subclasses fill in the specific steps
(abstract methods)
SECTION 6: INTERFACES — Rules, default/static Methods,
Functional Interfaces
Q11: What is an Interface in Java? How has it evolved from Java 7 to Java 8 to Java 9+?
Answer:
An interface is a contract — it defines what a class MUST do, without specifying HOW. It is the purest form
of abstraction.
Java 7 (classic interface — only constants and abstract methods):
interface Drawable {
double PI = 3.14; // implicitly public static final
void draw(); // implicitly public abstract
double area(); // implicitly public abstract
}
Java 8 additions — default and static methods:
interface Drawable {
void draw(); // abstract
default void describe() { // default method — has body
[Link]('I am a drawable shape');
}
static Drawable createDefault() { // static factory method in interface
return () -> [Link]('default shape');
}
}
Why default methods were added: Allowed Java to add new methods to existing interfaces (like
[Link](), [Link]()) without breaking ALL existing implementations.
Java 9 addition — private methods:
interface Drawable {
default void draw() { setup(); doDraw(); }
private void setup() { [Link]('setting up'); } // helper for
default methods
private static void log(String msg) { [Link](msg); }
}
Why private methods: Code reuse within the interface itself (avoid duplicating code across default
methods).
Key interface rules:
• All fields are implicitly public static final (constants)
• All abstract methods are implicitly public abstract
• Cannot have instance variables (no state)
• Cannot have constructors
• A class can implement MULTIPLE interfaces
• An interface can extend MULTIPLE interfaces
🔁 Follow-up: Can an interface extend another interface? (Yes — and it can extend multiple interfaces)
🔁 Follow-up: Can an interface have a constructor? (No)
🔁 Follow-up: What is a Marker Interface? Give examples.
– An interface with no methods — just marks a class. Examples: Serializable, Cloneable,
RandomAccess
– Java uses instanceof to check: if (obj instanceof Serializable)
Q12: What is the difference between an Abstract Class and an Interface? When do you choose
which?
Abstract Class Interface
Can have instance variables (state) Only public static final constants (no instance state)
Can have constructors Cannot have constructors
Methods: abstract + concrete + static + private Methods: abstract + default + static + private (Java 9+)
Single inheritance — extends one class Multiple implementation — implements many interfaces
Can have any access modifiers Methods are public by default
IS-A strong relationship CAN-DO / capability contract (Flyable, Serializable)
Use for shared base implementation + state Use for pure contract / capability across unrelated
classes
Example: AbstractList, HttpServlet Example: Comparable, Runnable, Serializable
Decision guide — which to use:
• Use Abstract Class when:
– Classes share significant common code / state
– You want to enforce a template pattern with some pre-built steps
– Tight IS-A relationship (Dog IS-A Animal, Student IS-A Person)
• Use Interface when:
– Defining a capability that unrelated classes share (Bird and Airplane both Flyable)
– You want multiple inheritance of type
– Defining a contract for dependency injection / loose coupling
– API design where you want to hide implementation completely
Real-world Spring Boot example:
// Interface for loose coupling — service doesn't depend on implementation
interface PaymentService { void processPayment(double amount); }
class StripePaymentService implements PaymentService { ... }
class PayPalPaymentService implements PaymentService { ... }
// Controller depends on PaymentService interface, not concrete class
🔁 Follow-up: Can an abstract class implement an interface? (Yes — and it doesn't have to implement all
interface methods)
🔁 Follow-up: If a class inherits from abstract class AND interface with same method name, what happens?
– Abstract class method takes priority over interface default method
Q13: What is a Functional Interface? What is @FunctionalInterface?
Answer:
A Functional Interface is an interface with EXACTLY ONE abstract method. It can have any number of
default, static, and private methods.
Functional interfaces are the foundation of Lambda expressions and method references in Java 8.
@FunctionalInterface annotation:
• Optional but recommended — tells the compiler to enforce the 'exactly one abstract method' rule
• If you accidentally add a second abstract method, compilation fails with a clear error
@FunctionalInterface
interface MathOperation {
int operate(int a, int b); // only ONE abstract method
default String describe() { return 'math op'; } // allowed
}
// Use with lambda
MathOperation add = (a, b) -> a + b;
MathOperation multiply = (a, b) -> a * b;
[Link]([Link](3, 4)); // 7
Built-in Functional Interfaces ([Link]):
• Predicate<T> — takes T, returns boolean: test(T t)
Predicate<String> isEmpty = String::isEmpty; [Link](''); // true
• Function<T, R> — takes T, returns R: apply(T t)
Function<String, Integer> len = String::length; [Link]('hello'); // 5
• Consumer<T> — takes T, returns void: accept(T t)
Consumer<String> print = [Link]::println;
• Supplier<T> — takes nothing, returns T: get()
Supplier<String> greeting = () -> 'Hello World';
• BiFunction<T, U, R> — takes T and U, returns R
• UnaryOperator<T> — Function<T, T> (same input and output type)
• BinaryOperator<T> — BiFunction<T, T, T>
🔁 Follow-up: What is a lambda expression? How is it related to functional interface?
– Lambda provides a concise way to implement a functional interface. It IS an anonymous
implementation of that interface.
🔁 Follow-up: What is a method reference? Give examples.
String::toUpperCase → s -> [Link]()
[Link]::println → s -> [Link](s)
Integer::new → n -> new Integer(n) (constructor reference)
SECTION 7: ENCAPSULATION — Access Modifiers,
Getters/Setters
Q14: Explain all Access Modifiers in Java. Give the visibility table.
Answer:
Access modifiers control the visibility/accessibility of classes, methods, and fields.
Java has four access modifiers:
Modifier Same Class Same Package Subclass (diff Other Package
pkg)
private ✅ YES ❌ NO ❌ NO ❌ NO
(default) / package- ✅ YES ✅ YES ❌ NO ❌ NO
private
protected ✅ YES ✅ YES ✅ YES ❌ NO
public ✅ YES ✅ YES ✅ YES ✅ YES
Best practices for Encapsulation:
• Always make fields private — never public unless constant (public static final)
• Provide public getters (read) and setters (write) only when needed
• Setters can include validation logic
public void setAge(int age) {
if (age < 0 || age > 150) throw new IllegalArgumentException('Invalid age: '
+ age);
[Link] = age;
}
Immutable class using encapsulation:
public final class Money { // final — cannot be subclassed
private final double amount; // private final — set once
private final String currency;
public Money(double amount, String currency) {
[Link] = amount;
[Link] = currency;
}
public double getAmount() { return amount; } // getter only, no setter
public String getCurrency() { return currency; }
}
🔁 Follow-up: What is the default access modifier if none is specified? (Package-private — accessible only
within same package)
🔁 Follow-up: Can a top-level class be private? (No — top-level classes can only be public or default. Only
nested/inner classes can be private)
🔁 Follow-up: What is the difference between protected and default?
– default: same package only; protected: same package + subclasses in other packages
SECTION 8: static, final, finally, finalize
Q15: Explain the 'static' keyword. What can be static in Java?
Answer:
The 'static' keyword means the member belongs to the CLASS itself, not to any specific instance.
Static members are shared across all instances and can be accessed without creating an object.
1. Static Variables (Class Variables):
class Counter {
static int count = 0; // ONE copy shared across all instances
int id;
Counter() { count++; [Link] = count; }
}
Counter c1 = new Counter(); Counter c2 = new Counter();
[Link]([Link]); // 2 — shared, not per object
2. Static Methods:
class MathUtils {
static int square(int n) { return n * n; } // no instance needed
}
[Link](5); // called on class, not object
Static method rules:
• Cannot use 'this' or 'super'
• Cannot access instance (non-static) variables or methods directly
• CAN be called on an object reference (but bad practice: [Link]())
3. Static Block (Static Initializer):
class DatabaseConfig {
static String url;
static { // runs ONCE when class is first loaded
url = [Link]('DB_URL');
[Link]('Config loaded');
}
}
4. Static Nested Class:
class Outer {
static class StaticNested { // does NOT need Outer instance
void display() { [Link]('Static Nested'); }
}
}
[Link] nested = new [Link](); // no Outer object needed
5. Static Import:
import static [Link].*;
double r = sqrt(16); // instead of [Link](16)
🔁 Follow-up: Can you override a static method? (No — you can only hide it)
🔁 Follow-up: What is the order of execution: static block vs constructor vs instance block?
– Order: Static block (once on class load) → Instance block → Constructor (both on each new)
🔁 Follow-up: Why is the main method static?
– JVM calls main() before any object exists — static is required so JVM can call it without creating an
instance
Q16: Explain the 'final' keyword in Java. All three uses.
Answer:
The 'final' keyword restricts modification. It can be applied to variables, methods, and classes.
1. Final Variable — value cannot be changed after assignment:
final int MAX_SIZE = 100;
MAX_SIZE = 200; // COMPILE ERROR
• Local final variable: must be assigned before use, cannot reassign
• Instance final variable: must be initialized in declaration OR in constructor
class Circle {
final double radius; // blank final — must assign in constructor
Circle(double r) { [Link] = r; }
}
• Static final: constant — convention is UPPER_SNAKE_CASE
public static final double PI = 3.14159;
Important: final reference vs final object:
final List<String> list = new ArrayList<>();
[Link]('hello'); // OK! The REFERENCE is final, not the object itself
list = new ArrayList<>(); // COMPILE ERROR — can't reassign the reference
2. Final Method — cannot be overridden by subclass:
class Parent {
final void criticalOperation() { /* cannot be changed */ }
}
class Child extends Parent {
@Override void criticalOperation() { } // COMPILE ERROR
}
Use case: Security-sensitive methods, methods in non-overridable logic ([Link]() is NOT final but
String is final)
3. Final Class — cannot be extended:
public final class String { ... } // nobody can extend String
public final class Integer { ... } // immutable wrapper classes
class MyString extends String { } // COMPILE ERROR
Use cases: Immutability (String, Integer), security (prevent subclass from overriding security checks)
🔁 Follow-up: What is the difference between final, finally, and finalize?
– final: keyword for constants/non-overridable methods/non-extendable classes
– finally: block in try-catch that always executes (cleanup code)
– finalize(): deprecated method in Object class, called by GC before object destruction
🔁 Follow-up: Can a final class implement an interface? (Yes — String implements Serializable, Comparable,
CharSequence)
🔁 Follow-up: Can a constructor be final? (No — constructors are not inherited so 'final' makes no sense)
SECTION 9: THE Object CLASS — equals, hashCode, toString,
clone
Q17: What are the important methods of the Object class? Explain equals() and hashCode()
contract.
Answer:
Every class in Java implicitly extends [Link]. These are the key Object methods:
1. toString() — string representation of object:
// Default: ClassName@hexHashCode e.g., Employee@1b6d3586
@Override
public String toString() {
return 'Employee{name=' + name + ', id=' + id + '}';
}
2. equals() — logical equality:
// Default: same as == (reference equality — same memory address)
@Override
public boolean equals(Object o) {
if (this == o) return true; // same reference
if (o == null || getClass() != [Link]()) return false; // null/type
check
Employee emp = (Employee) o; // cast
return id == [Link] && [Link](name, [Link]); // field comparison
}
3. hashCode() — integer representation for hash-based collections:
@Override
public int hashCode() {
return [Link](id, name); // best practice using [Link]()
}
THE CONTRACT between equals() and hashCode() — MEMORIZE THIS:
• Rule 1: If [Link](b) → [Link]() MUST == [Link]()
• Rule 2: If [Link]() == [Link]() → [Link](b) MAY be true or false (collision OK)
• Rule 3: If  → hashCodes MAY still be equal (collision — not ideal but valid)
Breaking this contract causes bugs in HashMap, HashSet, Hashtable.
4. clone() — creates a copy of the object:
• Class must implement Cloneable (marker interface)
• Default clone() performs SHALLOW copy (copies references, not nested objects)
• For deep copy: override clone() or use serialization/copy constructor
// Shallow copy — nested objects are NOT duplicated
Employee e1 = new Employee('John', new Address('NY'));
Employee e2 = (Employee) [Link]();
[Link]().setCity('LA'); // ALSO changes e1's address!
5. getClass() — returns runtime class:
[Link]().getName(); // '[Link]'
[Link]().getSimpleName(); // 'Employee'
6. wait() / notify() / notifyAll() — thread synchronization (on synchronized blocks)
🔁 Follow-up: What is the difference between == and equals()?
– ==: reference equality (same object in memory); equals(): logical equality (override to define)
🔁 Follow-up: What is shallow copy vs deep copy?
– Shallow: copies field values — object references are copied (not the objects they point to)
– Deep: copies everything recursively — completely independent copy
💡 Interview Tip: Always override both equals() AND hashCode() together — IDEs generate this. Lombok's
@EqualsAndHashCode also does it.
SECTION 10: INNER CLASSES & NESTED CLASSES
Q18: What are the types of Inner/Nested Classes in Java?
Answer:
Java supports four types of inner/nested classes:
1. Static Nested Class:
• Declared inside outer class with 'static'
• Does NOT need an instance of the outer class
• Cannot access outer class instance members (only static members)
class Outer {
static int x = 10;
static class StaticNested {
void display() { [Link](x); } // can access static members
}
}
[Link] sn = new [Link](); // no Outer instance needed
2. Non-static Inner Class (Regular Inner Class):
• Has access to ALL members (including private) of outer class
• Requires outer class instance to create
class Outer {
private int value = 42;
class Inner {
void display() { [Link](value); } // accesses private!
}
}
Outer outer = new Outer();
[Link] inner = [Link] Inner(); // needs Outer instance
3. Local Inner Class:
• Defined inside a method
• Scope limited to that method
• Can access final or effectively final local variables of enclosing method
void process() {
final int multiplier = 3; // effectively final
class LocalHelper {
int calc(int n) { return n * multiplier; }
}
[Link](new LocalHelper().calc(5)); // 15
}
4. Anonymous Inner Class:
• A class without a name, defined and instantiated in one expression
• Extends a class or implements an interface
• Common use: quick implementations, event listeners, Runnable
Runnable r = new Runnable() { // anonymous class implementing Runnable
@Override public void run() { [Link]('running'); }
};
// Java 8+ lambda equivalent:
Runnable r2 = () -> [Link]('running');
// Anonymous class for Comparator (pre Java 8):
[Link](list, new Comparator<String>() {
@Override public int compare(String a, String b) { return [Link](b); }
});
// Java 8+ lambda:
[Link]((a, b) -> [Link](b));
🔁 Follow-up: What is an effectively final variable in Java 8?
– A variable whose value never changes after initialization, even without 'final' keyword. Lambda and
anonymous class can use such variables.
🔁 Follow-up: When would you use a static nested class vs inner class?
– If the nested class doesn't need access to outer instance members → use static nested (less memory,
cleaner)
SECTION 11: SOLID PRINCIPLES
Q19: What are SOLID principles? Explain each with an example.
Answer:
SOLID is an acronym for five OOP design principles that make code maintainable, extensible, and robust.
S — Single Responsibility Principle (SRP):
A class should have ONE reason to change — one responsibility.
// BAD: UserService does too much
class UserService { void createUser() {} void sendEmail() {} void
generateReport() {} }
// GOOD: Separate responsibilities
class UserService { void createUser() {} }
class EmailService { void sendEmail() {} }
class ReportService { void generateReport() {} }
O — Open/Closed Principle (OCP):
Open for EXTENSION, Closed for MODIFICATION. Add new features by adding new code, not changing
existing code.
// GOOD: New payment types without changing existing code
interface PaymentProcessor { void process(double amount); }
class StripeProcessor implements PaymentProcessor { ... }
class PayPalProcessor implements PaymentProcessor { ... } // new — no existing
changes
L — Liskov Substitution Principle (LSP):
Subtypes must be substitutable for their base types without altering correctness.
// BAD: Square extends Rectangle but violates LSP
// Rectangle: setWidth(5), setHeight(3) → area = 15
// Square: setWidth(5) ALSO sets height to 5 → area = 25 (unexpected!)
// GOOD: Both implement Shape separately — no forced IS-A
interface Shape { double area(); }
class Rectangle implements Shape { ... }
class Square implements Shape { ... }
I — Interface Segregation Principle (ISP):
Clients should not be forced to implement methods they don't use. Prefer small, specific interfaces over fat
ones.
// BAD: Fat interface forces all classes to implement irrelevant methods
interface Animal { void fly(); void swim(); void walk(); }
// GOOD: Segregated interfaces
interface Flyable { void fly(); }
interface Swimmable { void swim(); }
interface Walkable { void walk(); }
class Duck implements Flyable, Swimmable, Walkable { ... }
class Fish implements Swimmable { ... } // only what it needs
D — Dependency Inversion Principle (DIP):
High-level modules should NOT depend on low-level modules. Both should depend on abstractions.
// BAD: OrderService directly depends on concrete MySQLDatabase
class OrderService { MySQLDatabase db = new MySQLDatabase(); }
// GOOD: Depend on abstraction — easy to swap implementation
interface Database { void save(Order order); }
class OrderService {
private final Database db;
OrderService(Database db) { [Link] = db; } // injected!
}
class MySQLDatabase implements Database { ... }
class MongoDatabase implements Database { ... }
This is exactly how Spring's Dependency Injection implements DIP!
🔁 Follow-up: Which SOLID principle does Spring's DI primarily implement? (DIP — Dependency Inversion)
🔁 Follow-up: How does OCP relate to design patterns? (Strategy, Decorator, and Factory patterns all follow
OCP)
💡 Interview Tip: In Accenture interviews, relate SOLID back to Spring Boot/real code — it shows practical
maturity.
SECTION 12: SCENARIO-BASED & TRICKY OOP QUESTIONS
Q20: TRICKY: What is the output of this code? (Polymorphism + constructor chain)
Code:
class A {
A() {
[Link]('A constructor');
show(); // <- KEY: polymorphic call in constructor!
}
void show() { [Link]('A show'); }
}
class B extends A {
int x = 10;
B() {
super(); // implicit — calls A()
[Link]('B constructor');
}
@Override void show() { [Link]('B show, x = ' + x); }
}
new B();
Output:
A constructor
B show, x = 0 <-- NOT 10!
B constructor
Explanation:
1. new B() → B's constructor called → super() (implicit) → A() runs
2. Inside A(), show() is called — runtime polymorphism! The actual object is B, so [Link]() is called
3. BUT B's instance variable x = 10 hasn't been assigned yet (instance initializers run AFTER super()) —
so x = 0 (default int value)
4. A() finishes → B's instance variable x assigned to 10
5. B() constructor body runs: 'B constructor'
**Lesson: Never call overridable methods in a constructor!** It leads to partially-initialized state being
exposed.
💡 Interview Tip: This is a HIGH-FREQUENCY tricky question. The answer 'B show, x = 0' surprises most
people.
🔁 Follow-up: How do you prevent this problem? (Make the method private or final in the parent class —
private/final methods are not overridden)
Q21: TRICKY: What is the difference between == and equals() for String? What is String Pool?
Answer:
String is one of the most common OOP questions in interviews.
String s1 = 'hello'; // String literal — stored in String Pool
String s2 = 'hello'; // same literal — reuses existing Pool object
String s3 = new String('hello'); // new keyword — always creates new heap object
[Link](s1 == s2); // TRUE — same Pool reference
[Link](s1 == s3); // FALSE — different heap objects
[Link]([Link](s3)); // TRUE — same content
String Pool (Interning):
• String Pool (part of Heap since Java 7; was PermGen in Java 6) stores unique string literals
• When you write 'hello', JVM checks the pool — if found, returns same reference; if not, creates new
• [Link]() method manually adds a string to the pool and returns the pooled reference
String s4 = new String('hello').intern(); // s1 == s4 → TRUE
Why String is immutable:
• Security — string is used for class loading, passwords, network connections
• Thread safety — immutable objects are inherently thread-safe
• String Pool efficiency — shared safely only if immutable
• HashCode caching — String caches its hashCode for performance
🔁 Follow-up: How many String objects are created: String s = new String('abc');?
– Up to 2: 'abc' in pool (if not already there) + new String on heap
🔁 Follow-up: What is StringBuilder vs StringBuffer vs String?
– String: immutable; StringBuilder: mutable, NOT thread-safe, faster; StringBuffer: mutable, thread-safe
(synchronized), slower
🔁 Follow-up: Why is String a final class?
– Prevents subclasses from overriding behavior (e.g., making it mutable), ensuring security and pool
safety
Q22: SCENARIO: Design a Singleton class in Java. What are the thread-safe ways?
Answer:
Singleton ensures only ONE instance of a class exists in the JVM. Multiple approaches:
1. Eager Initialization (Thread-Safe — simplest):
public class Singleton {
private static final Singleton INSTANCE = new Singleton(); // created at
class load
private Singleton() {} // private constructor
public static Singleton getInstance() { return INSTANCE; }
}
Downside: Instance created even if never used.
2. Lazy Initialization with Double-Checked Locking (Thread-Safe + Lazy):
public class Singleton {
private static volatile Singleton instance; // volatile — CRITICAL for
visibility
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // First check (no locking)
synchronized ([Link]) { // Lock only when null
if (instance == null) { // Second check (inside lock)
instance = new Singleton();
}
}
}
return instance;
}
}
Why volatile? Without it, CPU instruction reordering can return a partially-constructed instance.
3. Bill Pugh / Initialization-on-Demand Holder (BEST — Thread-Safe + Lazy + No sync overhead):
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() { return [Link]; }
}
Why it works: Inner class loaded only when getInstance() is called. JVM guarantees class loading is
thread-safe.
4. Enum Singleton (Simplest + Serialization-Safe):
public enum Singleton {
INSTANCE;
public void doSomething() { ... }
}
[Link]();
Enum handles serialization, reflection attacks, and thread safety automatically. Recommended by Josh
Bloch (Effective Java).
🔁 Follow-up: How can Reflection break Singleton? How to prevent?
– Reflection can call private constructor. Prevention: throw exception in constructor if instance already
exists.
🔁 Follow-up: How can Serialization break Singleton? How to prevent?
– Deserialization creates a new instance. Prevention: implement readResolve() { return INSTANCE; }
🔁 Follow-up: What is Spring's @Singleton scope? Is it same as Singleton pattern?
– Spring Singleton: one instance per ApplicationContext (not per JVM). Singleton pattern: one per JVM.
Q23: SCENARIO: Explain IS-A vs HAS-A. When to prefer Composition over Inheritance?
Answer:
IS-A (Inheritance):
• Dog IS-A Animal → use extends
• Toyota IS-A Car → use extends
• Strong coupling — child depends heavily on parent
HAS-A (Composition/Aggregation):
• Car HAS-A Engine → Engine is a field in Car
• OrderService HAS-A PaymentService → injected as dependency
• Loose coupling — components can be swapped independently
Composition over Inheritance — Favor composition:
// BAD — Inheritance used wrongly (Stack IS-A Vector? Conceptually wrong!)
class Stack extends Vector { ... } // This is Java's actual Stack — considered a
design mistake!
[Link](0, element); // exposed Vector methods that violate Stack contract!
// GOOD — Composition (Stack USES a list internally)
class Stack<T> {
private final Deque<T> store = new ArrayDeque<>();
public void push(T item) { [Link](item); }
public T pop() { return [Link](); }
public T peek() { return [Link](); }
// ONLY Stack operations exposed — clean contract!
}
When Composition wins over Inheritance:
• You want to reuse code but the IS-A relationship isn't truly valid
• You need to change behaviour at runtime (Strategy pattern uses composition)
• The parent class is not designed for inheritance
• You want to avoid tight coupling and the 'fragile base class' problem
**Rule of thumb:** Before using inheritance, ask: 'Is this truly IS-A or just similar behaviour?' If in doubt,
use composition.
🔁 Follow-up: What is the 'fragile base class' problem?
– Changes to a parent class can break subclasses unexpectedly, especially when internal
implementation details change
Q24: TRICKY: Can we instantiate an interface? What is anonymous class? Can interfaces have
constructors?
Answer:
Can we instantiate an interface? Directly — NO. Via anonymous class — YES.
interface Greeting {
void greet(String name);
}
// Direct instantiation — COMPILE ERROR
Greeting g = new Greeting(); // ERROR
// Via anonymous class — OK!
Greeting g = new Greeting() {
@Override public void greet(String name) { [Link]('Hello, ' +
name); }
};
[Link]('Alice'); // Hello, Alice
// Via lambda (if Greeting is @FunctionalInterface) — Java 8+
Greeting g2 = name -> [Link]('Hello, ' + name);
What exactly happens with the anonymous class?
• Java creates a hidden class (e.g., Greeting$[Link]) that implements the interface
• An instance of THAT hidden class is created
• You're not instantiating the interface — you're instantiating an anonymous implementation
Can interfaces have constructors?
NO — Interfaces cannot have constructors because they cannot be directly instantiated.
Even with default methods in Java 8, interfaces still have no constructor.
🔁 Follow-up: What is the difference between anonymous class and lambda expression?
– Lambda: only for @FunctionalInterface (1 abstract method), no state, concise
– Anonymous class: any interface/abstract class, can have state (fields), more verbose
– Lambda creates a method reference; anonymous class creates a full class
SECTION 13: QUICK REFERENCE CHEAT SHEET
OOP Concepts Master Table
Concept Key Points to Remember
4 Pillars Encapsulation, Inheritance, Polymorphism, Abstraction
Encapsulation private fields + public getters/setters. Data hiding.
Inheritance extends (class), implements (interface). IS-A. Root =
Object.
Polymorphism Overloading = compile-time. Overriding = runtime.
@Override.
Abstraction Abstract class (partial impl) vs Interface (pure contract).
Abstract class Cannot instantiate. Can have constructor + state +
concrete methods.
Interface No state. No constructor. default/static methods since
Java 8.
Overriding rules Cannot override: private, static, final methods or
constructors.
Covariant return Child can return subtype of parent's return type in
override.
Method Hiding Static methods are hidden (not overridden). Compile-
time resolution.
Access: private Same class only.
Access: default Same package only.
Access: protected Same package + subclasses.
Access: public Everywhere.
final variable Value cannot change after assignment (reference final,
not object).
final method Cannot be overridden.
final class Cannot be extended (String, Integer).
static Belongs to class, not instance. No 'this'. Called via class
name.
equals + hashCode Override both together. Contract: equals → same
hashCode.
this() Constructor chaining within same class. Must be first
statement.
super() Call parent constructor. Must be first statement. Auto-
added if omitted.
SOLID-S Single Responsibility — one class, one reason to
change.
SOLID-O Open/Closed — extend without modifying existing code.
SOLID-L Liskov — subtype fully substitutes parent without
breaking behavior.
SOLID-I Interface Segregation — small interfaces over fat ones.
SOLID-D Dependency Inversion — depend on abstractions
(Spring DI = DIP).
OOPs mastered! You're one step closer to cracking it! 🚀
Next up: Java Streams | Exception Handling
JAVA EXCEPTION HANDLING
Focused Interview Preparation Guide
Custom Software Engineer | Accenture | 3+ Years Experience Level
SECTION 1: EXCEPTION HIERARCHY & TYPES
Q1: What is the Exception Hierarchy in Java? What is the difference between Checked and
Unchecked exceptions?
Answer:
The entire exception hierarchy is rooted at [Link], which has two direct subclasses:
Throwable
• Error — Serious JVM/system-level problems. NOT meant to be caught by application code.
– Examples: OutOfMemoryError, StackOverflowError, VirtualMachineError
– These indicate the JVM is in an unrecoverable state
• Exception — Application-level problems that CAN be caught and handled
– RuntimeException (and subclasses) → UNCHECKED
– Everything else under Exception → CHECKED
Checked Exceptions:
• Must be either caught (try-catch) OR declared (throws clause) — compiler enforces this
• Represent recoverable external conditions: file missing, network failure, DB unavailable
• Examples: IOException, FileNotFoundException, SQLException, ClassNotFoundException,
ParseException
// Compiler forces you to handle this
public void readFile(String path) throws IOException { // declared
FileReader fr = new FileReader(path); // throws FileNotFoundException
}
Unchecked Exceptions (RuntimeException subclasses):
• NOT required to be caught or declared — compiler doesn't check
• Represent programming bugs / logic errors that should be fixed, not caught
• Examples: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException,
ClassCastException, NumberFormatException, ArithmeticException, ConcurrentModificationException
int[] arr = {1,2,3};
arr[5] = 10; // ArrayIndexOutOfBoundsException — no compile error, blows up at
runtime
Error vs Exception:
• Error: JVM-level, unrecoverable (don't catch errors in production code, except for logging at top
level)
• Exception: Application-level, recoverable
🔁 Follow-up: Can you catch an Error? (Technically yes — it's a Throwable — but you almost never should)
🔁 Follow-up: Is NullPointerException checked or unchecked? (Unchecked — extends RuntimeException)
🔁 Follow-up: What is the difference between Exception and RuntimeException?
– Exception subclasses (not RuntimeException) = checked. RuntimeException subclasses = unchecked.
💡 Tip: Draw the hierarchy tree: Throwable → Error/Exception → RuntimeException. Interviewers love this.
SECTION 2: try, catch, finally, try-with-resources
Q2: Explain try-catch-finally. What are the rules? What happens when finally has a return
statement?
Answer:
Basic structure:
try {
// risky code that may throw an exception
} catch (FileNotFoundException e) { // specific — catch first
// handle file not found
} catch (IOException e) { // general — catch after specific
// handle any other IO exception
} catch (Exception e) { // most general — catches all
// handle any exception
} finally {
// ALWAYS executes — cleanup code (close connections, release resources)
}
finally block rules:
• Executes whether exception occurs or not
• Executes even if catch block has a return statement
• Does NOT execute if: [Link]() is called, or JVM crashes
TRICKY — What if finally has a return statement?
public int tricky() {
try {
return 1;
} finally {
return 2; // overrides try's return!
}
}
// Output: 2 — finally's return OVERRIDES the try's return
TRICKY — Exception swallowing:
public int swallowed() {
try {
throw new RuntimeException('original');
} finally {
return 42; // exception is SWALLOWED — never propagates!
}
}
// No exception thrown! returns 42. The original exception is LOST.
This is a dangerous anti-pattern — avoid return/throw in finally blocks.
Multi-catch (Java 7+):
} catch (IOException | SQLException e) { // pipe-separated multi-catch
[Link]('Data error', e);
}
• e is effectively final — cannot reassign inside multi-catch block
🔁 Follow-up: Can you have try without catch? (Yes — try with finally only. Valid.)
🔁 Follow-up: Can you have try without finally? (Yes — try with at least one catch.)
🔁 Follow-up: Can try block be empty? (Syntactically yes, but it's pointless and bad practice)
Q3: What is try-with-resources? How does it work? What is AutoCloseable?
Answer:
try-with-resources (Java 7+) automatically closes resources (connections, streams, etc.) when the try
block exits — no explicit finally block needed.
Old way (verbose, error-prone):
Connection conn = null;
try {
conn = [Link]();
// use connection
} catch (SQLException e) {
[Link]();
} finally {
if (conn != null) { // easy to forget null check!
try { [Link](); } catch (SQLException e) { /* ignore? log? */ }
}
}
New way — try-with-resources:
try (Connection conn = [Link]();
PreparedStatement ps = [Link](sql)) { // multiple resources
// use conn and ps
} catch (SQLException e) {
[Link]('DB error', e);
}
// conn and ps are AUTOMATICALLY closed in reverse order — even if exception
occurs
AutoCloseable interface:
Any class that implements AutoCloseable (or its subinterface Closeable) can be used in try-with-
resources.
public interface AutoCloseable {
void close() throws Exception;
}
• Closeable (for streams) extends AutoCloseable — close() throws IOException
• Examples: Connection, PreparedStatement, FileReader, InputStream, Scanner
Custom resource with AutoCloseable:
public class DatabaseSession implements AutoCloseable {
public DatabaseSession() { [Link]('Session opened'); }
@Override
public void close() { [Link]('Session closed automatically'); }
}
try (DatabaseSession session = new DatabaseSession()) {
// use session
} // close() called automatically here
Suppressed Exceptions:
If both try block AND close() throw exceptions, the try block's exception is primary and close()'s exception
is suppressed (attached to primary via addSuppressed()).
Throwable[] suppressed = [Link]();
🔁 Follow-up: What is the order of closing in try-with-resources with multiple resources?
– Reverse order of declaration: last opened, first closed
🔁 Follow-up: What is the difference between Closeable and AutoCloseable?
– Closeable: throws IOException; AutoCloseable: throws Exception (broader)
SECTION 3: throw, throws & Custom Exceptions
Q4: What is the difference between throw and throws? When do you use each?
Answer:
throw — to ACTUALLY THROW an exception (inside method body):
• Used to explicitly throw an exception instance
• Can only throw one exception at a time
• Followed by an exception instance (object)
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException('Age cannot be negative: ' + age);
}
[Link] = age;
}
throws — to DECLARE that a method MAY throw exception(s) (in method signature):
• Part of the method signature — warns callers to handle it
• Required for checked exceptions (compiler enforces)
• Can list multiple exceptions separated by commas
public void readFile(String path) throws IOException, FileNotFoundException {
// method may throw these — caller must handle or re-declare
}
throws for unchecked exceptions — not required but good documentation:
public int divide(int a, int b) throws ArithmeticException { // optional for
unchecked
if (b == 0) throw new ArithmeticException('Division by zero');
return a / b;
}
Summary:
• throw → action (used in method body to throw an exception NOW)
• throws → declaration (used in method signature to warn about POSSIBLE exceptions)
🔁 Follow-up: Can you throw a checked exception from a method that doesn't declare it in throws?
– No — compile error for checked exceptions. Unchecked exceptions can always be thrown without
declaring.
🔁 Follow-up: Can you use throw without new? (No — throw requires an exception INSTANCE)
Q5: How do you create a Custom Exception? When should you create one?
Answer:
Custom exceptions make your code more expressive and meaningful. They describe business-specific
failure scenarios.
Creating a Custom Checked Exception:
public class InsufficientFundsException extends Exception {
private final double amount; // extra context
public InsufficientFundsException(String message) {
super(message);
[Link] = 0;
}
public InsufficientFundsException(String message, double amount) {
super(message);
[Link] = amount;
}
public InsufficientFundsException(String message, Throwable cause) {
super(message, cause); // preserves the original cause — IMPORTANT!
[Link] = 0;
}
public double getAmount() { return amount; }
}
Creating a Custom Unchecked Exception:
public class UserNotFoundException extends RuntimeException {
private final Long userId;
public UserNotFoundException(Long userId) {
super('User not found with ID: ' + userId);
[Link] = userId;
}
public Long getUserId() { return userId; }
}
Usage:
public User findUser(Long id) {
return [Link](id)
.orElseThrow(() -> new UserNotFoundException(id));
}
When to create custom exceptions:
• Business rule violations (InsufficientFundsException, OrderAlreadyCancelledException)
• Domain-specific error signaling (PaymentDeclinedException, InvalidCouponException)
• When you need to carry extra context data (userId, orderId in the exception)
• When existing exceptions don't convey enough meaning
Best practices:
• Always provide constructor with (String message, Throwable cause) — preserves stack trace
• Prefer extending RuntimeException for Spring Boot apps (avoids checked exception noise)
• Name clearly: ends with 'Exception'
🔁 Follow-up: Why is it important to pass 'cause' to super() in custom exceptions?
– Without it, the original exception and its stack trace are lost — makes debugging very hard (exception
chaining)
🔁 Follow-up: In Spring Boot, what type of exception does @ControllerAdvice handle best?
– Unchecked (RuntimeException subclasses) — clean, no try-catch needed in service layer
SECTION 4: KEY CONCEPTS — Chaining, Re-throwing, Best Practices
Q6: What is Exception Chaining? What is the difference between [Link]() and
[Link]()?
Answer:
Exception Chaining (wrapping low-level exceptions with meaningful ones):
When catching a low-level exception (e.g., SQLException), you wrap it in a higher-level business
exception while PRESERVING the original cause.
public User loadUser(Long id) {
try {
return [Link](SQL, userMapper, id);
} catch (EmptyResultDataAccessException e) {
throw new UserNotFoundException(id); // business exception
} catch (DataAccessException e) {
throw new ServiceException('Failed to load user ' + id, e); // chain
with cause
}
}
Why exception chaining matters:
• Hides implementation details from callers (caller doesn't need to know about SQLException)
• Provides business-meaningful error messages
• Preserves full diagnostic info (original stack trace accessible via getCause())
Exception methods:
• [Link]() — returns only the message string of THIS exception
• [Link]() — localized version (override for i18n)
• [Link]() — returns the original wrapped exception (or null if none)
• [Link]() — prints FULL stack trace to stderr (include cause chain)
• [Link]() — returns class name + message
• [Link]() — returns StackTraceElement[] array
try { ... }
catch (ServiceException e) {
[Link]([Link]()); // 'Failed to load user 5'
[Link]('Caused by: ' + [Link]().getMessage()); // original DB error
}
🔁 Follow-up: Should you use [Link]() in production code?
– NO. Use a logging framework (SLF4J + Logback). printStackTrace() goes to stderr, not logs.
– Use: [Link]('Error loading user', e) — logs message AND full stack trace properly
Q7: What is Re-throwing? What is the difference between 'throw e' and 'throw new
Exception(e)'?
Answer:
Re-throwing the same exception:
try {
riskyOperation();
} catch (IOException e) {
[Link]('Operation failed', e);
throw e; // re-throw SAME exception — original stack trace preserved
}
Wrapping in new exception (exception chaining):
try {
riskyOperation();
} catch (IOException e) {
throw new ServiceException('High-level failure', e); // wraps original as
cause
}
throw e vs throw new Exception(e):
• throw e — preserves original exception type and stack trace exactly
• throw new Exception(e) — creates NEW exception; original is attached as cause; adds new stack
frame
Re-throwing without losing type information (Java 7+):
public <T extends Exception> void rethrow(T e) throws T {
throw e; // type-safe rethrow
}
Anti-pattern — catching and swallowing silently:
// BAD — worst practice! Exception is lost
try {
risky();
} catch (Exception e) {
// empty catch block — NEVER DO THIS
}
// BAD — only printing, no logging, no rethrow
} catch (Exception e) { [Link](); }
🔁 Follow-up: When is it acceptable to swallow an exception?
– Almost never. At minimum, log it. Acceptable only for truly expected, non-impactful cases (e.g.,
optional feature unavailable).
SECTION 5: CRITICAL DISTINCTIONS & TRICKY QUESTIONS
Q8: What is the difference between final, finally, and finalize?
Keyword Description & Key Points
final KEYWORD — restricts modification. • final variable: value
cannot change after assignment • final method: cannot be
overridden • final class: cannot be extended (String, Integer)
finally BLOCK — in try-catch-finally. • Always executes (exception
or no exception) • Used for cleanup (closing resources) •
Does NOT execute if [Link]() called • Overrides
try/catch return if it has a return statement
finalize() METHOD — in [Link] (DEPRECATED since Java
9). • Called by GC before object is garbage collected •
Unreliable — no guarantee when or if it runs • Use Cleaner
(Java 9+) or try-with-resources instead
// final
final int MAX = 100;
final class String { }
final void method() { }
// finally
try { riskyCode(); }
catch (Exception e) { handle(e); }
finally { cleanup(); } // ALWAYS runs
// finalize (DEPRECATED — do not use)
@Override
protected void finalize() throws Throwable {
// called by GC — but don't rely on this
}
💡 Tip: These three are asked together in nearly every Java interview. The trick is to describe finalize() as
DEPRECATED.
Q9: Can you have a try block without catch? What are all valid try combinations?
Answer:
Java allows several combinations for try blocks. Rules:
• try must be followed by catch, finally, or both
• try alone is NOT valid
Valid combinations:
// 1. try + catch (most common)
try { risky(); } catch (Exception e) { handle(e); }
// 2. try + finally (no catch — exception propagates, finally still runs)
try { risky(); } finally { cleanup(); }
// 3. try + catch + finally
try { risky(); } catch (Exception e) { handle(e); } finally { cleanup(); }
// 4. try + multiple catches
try { risky(); }
catch (FileNotFoundException e) { }
catch (IOException e) { }
catch (Exception e) { }
// 5. try + multi-catch (Java 7+)
try { risky(); } catch (IOException | SQLException e) { handle(e); }
// 6. try-with-resources (Java 7+)
try (Resource r = new Resource()) { use(r); } // catch and finally optional here
INVALID:
try { } // compile error — no catch or finally
Order of catch blocks matters:
• More specific exceptions FIRST, more general LAST
catch (FileNotFoundException e) { } // specific — FIRST
catch (IOException e) { } // general — AFTER
// If reversed: compile error — 'FileNotFoundException already caught by
IOException'
🔁 Follow-up: What happens if you don't catch an exception? (It propagates up the call stack. If unhandled, JVM
terminates thread and prints stack trace)
Q10: What is StackOverflowError? What is OutOfMemoryError? Are they catchable?
Answer:
Both are [Link] subclasses — JVM-level issues.
StackOverflowError:
• Thrown when the call stack exceeds its limit — most common cause: INFINITE RECURSION
// Classic StackOverflow — recursive method with no base case
public int factorial(int n) {
return n * factorial(n - 1); // missing base case: if (n==0) return 1
}
• Each method call adds a stack frame. Too many frames = StackOverflowError
• Stack size configurable via JVM flag: -Xss512k
OutOfMemoryError:
• Thrown when JVM cannot allocate more heap memory
• Common subtypes:
– Java heap space: too many objects, memory leak
– GC overhead limit exceeded: GC spending >98% time reclaiming <2% memory
– Metaspace: too many classes loaded (common in apps with heavy reflection/classloading)
• Heap size configurable: -Xms256m (initial) -Xmx2g (maximum)
Are they catchable?
Technically yes — they are Throwable. But you should almost never catch them:
try {
infiniteRecursion();
} catch (StackOverflowError e) {
[Link]('Caught it!'); // technically works
}
In practice: Catching OOM or SOE is dangerous — the JVM is in an undefined state. Acceptable only for
top-level logging before graceful shutdown.
🔁 Follow-up: What causes a memory leak in Java? (Objects still referenced but no longer needed — held in
static collections, listeners not deregistered, ThreadLocal not cleaned)
🔁 Follow-up: How do you diagnose OutOfMemoryError? (Heap dump analysis with VisualVM/Eclipse MAT, -
XX:+HeapDumpOnOutOfMemoryError JVM flag)
SECTION 6: SCENARIO-BASED QUESTIONS
Q11: SCENARIO: What is the output of this code? (finally + return tricky question)
Code:
public class Test {
public static int getValue() {
int x = 10;
try {
x = 20;
return x; // x = 20, prepared to return
} finally {
x = 30; // x changes to 30...
// but NO return here
}
}
public static void main(String[] args) {
[Link](getValue());
}
}
Output: 20
Explanation:
When 'return x' is hit in try, the CURRENT VALUE of x (20) is saved as the return value. Then finally runs
and changes x to 30 — but the already-captured return value (20) is unchanged. Since finally has no
return, the saved value 20 is returned.
If finally had 'return x;' it would return 30 (finally's return overrides try's return).
💡 Tip: The key insight: 'return value is captured before finally runs'. If finally doesn't return, the captured value is
used.
Q12: SCENARIO: Exception handling in a Spring Boot service — what's wrong with this code?
Code (has multiple issues):
@Service
public class OrderService {
public Order createOrder(Long userId, List<Item> items) {
try {
User user = [Link](userId).get(); // Issue 1
Order order = new Order(user, items);
return [Link](order);
} catch (Exception e) { // Issue 2
[Link](); // Issue 3
return null; // Issue 4
}
}
}
Issues identified:
• Issue 1 — .get() on Optional: throws NoSuchElementException if user not found. Should
use .orElseThrow()
• Issue 2 — Catching generic Exception: too broad, catches things you don't intend (NullPointer,
runtime bugs)
• Issue 3 — [Link](): never use in production. Use SLF4J logger
• Issue 4 — Returning null: caller must null-check. Unexpected null causes NullPointerException
elsewhere
Fixed version:
@Service
public class OrderService {
private static final Logger log =
[Link]([Link]);
public Order createOrder(Long userId, List<Item> items) {
User user = [Link](userId)
.orElseThrow(() -> new UserNotFoundException(userId)); // meaningful
exception
if (items == null || [Link]()) {
throw new IllegalArgumentException('Order must have at least one
item');
}
Order order = new Order(user, items);
return [Link](order); // let DataAccessException propagate —
handle at @ControllerAdvice
}
}
Exceptions bubble up to @RestControllerAdvice for uniform HTTP response handling.
💡 Tip: This scenario tests real-world production awareness — being able to spot bad patterns is as valuable as
writing good ones.
Q13: SCENARIO: What is NullPointerException? How do you prevent it? What changed in Java
14+?
Answer:
NullPointerException (NPE) is the most common RuntimeException — thrown when you try to use a null
reference.
Common causes:
String s = null;
[Link](); // NPE
[Link](); // NPE
int[] arr = null;
arr[0] = 1; // NPE
((String) null).length(); // NPE after cast
Prevention strategies:
• 1. Null checks (traditional):
if (user != null && [Link]() != null) { ... }
• 2. Optional<T> (Java 8+) — explicit null handling:
Optional<User> user = [Link](id);
String name = [Link](User::getName).orElse('Unknown');
[Link](u -> sendEmail(u));
[Link](() -> new UserNotFoundException(id));
• 3. [Link]() — fail fast with clear message:
public OrderService(UserRepo userRepo) {
[Link] = [Link](userRepo, 'userRepo must not be
null');
}
• 4. @NonNull / @NotNull annotations (Lombok, JSR-305) — IDE/static analysis warnings
• 5. Defensive coding — return empty collections instead of null:
// BAD
public List<Order> getOrders(Long userId) { return null; }
// GOOD
public List<Order> getOrders(Long userId) { return [Link](); }
Java 14+ Helpful NPE Messages:
JVM now tells you EXACTLY which variable was null:
// Old Java:
// Exception in thread 'main' [Link]
// Java 14+:
// Cannot invoke '[Link]()' because '[Link]' is null
Enabled by default from Java 17+.
🔁 Follow-up: What is Optional? Is it meant to replace all null checks?
– Optional is for return types where absence is possible. Don't use for method params or fields.
🔁 Follow-up: When should you NOT use Optional? (For fields, parameters, collections — use empty collections
instead)
SECTION 7: BEST PRACTICES QUICK REFERENCE
✅ DO — Best Practices ❌ DON'T — Anti-patterns
Catch specific exceptions first, general last Catch Exception or Throwable everywhere (too broad)
Use try-with-resources for any AutoCloseable Use finally{} manually for resource closing
Log with SLF4J: [Link]('msg', e) Use [Link]() in production
Preserve cause: throw new MyEx('msg', e) Swallow exceptions with empty catch blocks
Return Optional / empty collections, not null Return null from methods that may fail
Create custom exceptions with extra context Use generic Exception with vague messages
Use @ControllerAdvice for REST error handling Try-catch in every controller method
Throw exceptions early (fail fast) Ignore errors and let them silently corrupt state
Use unchecked (RuntimeException) in Spring apps Declare checked exceptions all the way up the stack
Include both message AND cause in custom Lose the original stack trace by not passing cause
exceptions
Exception Handling — Done! 🎯
Next up: Java Streams — the most asked Java 8 topic!
STREAMS • GENERICS • FUNCTIONAL
INTERFACES
Java 8+ — Complete Interview Preparation Guide
Custom Software Engineer | Accenture | 3+ Years Experience Level
PART A : JAVA STREAMS
SECTION 1: What is a Stream? Intermediate vs Terminal Operations
Q1: What is a Stream in Java 8? How is it different from a Collection?
Answer:
A Stream is a sequence of elements that supports sequential and parallel aggregate operations. It is NOT
a data structure — it does not store data. It is a pipeline for processing data.
Key differences:
• Collection — stores data in memory; can be iterated multiple times; represents data
• Stream — computes on demand; consumed once; represents computation pipeline
Stream characteristics:
• Lazy evaluation — intermediate operations are not executed until a terminal operation is called
• Single use — once a Stream is consumed (terminal op called), it cannot be reused
• Non-destructive — stream operations do not modify the original data source
• Can be sequential or parallel
Stream pipeline structure:
Source → Intermediate Operations (lazy) → Terminal Operation (triggers execution)
List<String> names = [Link]('Alice', 'Bob', 'Charlie', 'Anna');
long count = [Link]() // 1. Source — creates stream
.filter(n -> [Link]('A')) // 2. Intermediate — lazy
.map(String::toUpperCase) // 3. Intermediate — lazy
.count(); // 4. Terminal — triggers execution
// count = 2
Ways to create a Stream:
[Link]() // from Collection
[Link](array) // from array
[Link]('a', 'b', 'c') // from values
[Link](0, n -> n + 2) // infinite stream
[Link](Math::random) // infinite stream from Supplier
[Link](0, 10) // primitive stream 0..9
[Link](1, 10) // 1..10 inclusive
[Link]([Link]('[Link]')) // from file lines
🔁 Follow-up: Can a Stream be reused? (No — calling a terminal op closes it. Create a new stream each time)
🔁 Follow-up: What is a primitive stream? Why does it exist?
– IntStream, LongStream, DoubleStream avoid boxing/unboxing overhead of Stream<Integer>
Q2: What is the difference between Intermediate and Terminal operations? Give examples of
each.
Answer:
Intermediate Operations — lazy, return a new Stream:
They are not executed until a terminal operation is invoked. They build up the pipeline.
• filter(Predicate) — keep elements matching condition
.filter(s -> [Link]() > 3)
• map(Function) — transform each element to another type
.map(String::toUpperCase) .map(User::getName)
• flatMap(Function) — flatten nested streams into one
.flatMap(list -> [Link]()) // List<List<T>> → Stream<T>
• distinct() — remove duplicates (uses equals/hashCode)
• sorted() / sorted(Comparator) — sort elements
• limit(n) — take first n elements
• skip(n) — skip first n elements
• peek(Consumer) — debug/inspect without consuming
.peek(e -> [Link]('Processing: {}', e))
• mapToInt / mapToLong / mapToDouble — convert to primitive stream
Terminal Operations — eager, trigger pipeline execution, consume the stream:
• collect(Collector) — gather into List, Set, Map, String, etc.
.collect([Link]())
• count() — count elements
• forEach(Consumer) — perform action on each element
• findFirst() / findAny() — return Optional of first/any element
• anyMatch / allMatch / noneMatch (Predicate) — boolean short-circuit checks
.anyMatch(s -> [Link]('A')) // true if ANY matches
.allMatch(s -> [Link]() > 2) // true if ALL match
.noneMatch(s -> [Link]()) // true if NONE match
• reduce(identity, BinaryOperator) — fold stream into single value
.reduce(0, Integer::sum) // sum all elements
• min(Comparator) / max(Comparator) — return Optional<T>
• toArray() — convert to array
• sum() / average() / summaryStatistics() — on primitive streams only
🔁 Follow-up: What is peek() used for? (Debug/logging — should not produce side effects)
🔁 Follow-up: What is the difference between map() and flatMap()?
– map: one-to-one transformation. flatMap: one-to-many, flattens the result.
Stream<List<String>> nested → .flatMap(Collection::stream) → Stream<String>
💡 Tip: Be ready to explain lazy evaluation with an example: stream ops are not called until terminal op fires.
SECTION 2: Collectors — the most important terminal operation
Q3: Explain Collectors in detail. What are the most important ones?
Answer:
Collectors are used with collect() to accumulate stream elements into various forms.
Basic collectors:
List<String> list = [Link]([Link]());
Set<String> set = [Link]([Link]());
String joined = [Link]([Link](', ', '[', ']'));
// joining([Alice, Bob, Charlie])
Counting & statistics:
long count = [Link]([Link]());
int sum = [Link]([Link](Employee::getSalary));
double avg = [Link]([Link](Employee::getSalary));
IntSummaryStatistics stats =
[Link]([Link](Employee::getSalary));
// [Link](), getMax(), getSum(), getAverage(), getCount()
Grouping — VERY COMMON in interviews:
// Group employees by department
Map<String, List<Employee>> byDept =
[Link]()
.collect([Link](Employee::getDepartment));
// Group and count per department
Map<String, Long> countByDept =
[Link]()
.collect([Link](Employee::getDepartment,
[Link]()));
// Group and get average salary per department
Map<String, Double> avgSalaryByDept =
[Link]()
.collect([Link](Employee::getDepartment,
[Link](Employee::getSalary)));
Partitioning — split into true/false groups:
Map<Boolean, List<Employee>> partitioned =
[Link]()
.collect([Link](e -> [Link]() > 50000));
List<Employee> highPaid = [Link](true);
List<Employee> lowPaid = [Link](false);
toMap:
Map<Long, String> idToName = [Link]()
.collect([Link](Employee::getId, Employee::getName));
// Handle duplicate keys with merge function
Map<String, String> deptToNames = [Link]()
.collect([Link](
Employee::getDepartment,
Employee::getName,
(existing, newVal) -> existing + ', ' + newVal)); // merge duplicates
Java 16+ — toUnmodifiableList/Set/Map:
List<String> immutable = [Link]([Link]());
🔁 Follow-up: What is the difference between groupingBy and partitioningBy?
– groupingBy: any key type, Map<K, List<T>>; partitioningBy: only boolean keys, Map<Boolean,
List<T>>
🔁 Follow-up: What happens with toMap if there are duplicate keys? (Throws IllegalStateException — must
provide merge function)
SECTION 3: reduce, flatMap, Optional & Parallel Streams
Q4: Explain reduce() in Java Streams. What are the overloads?
Answer:
reduce() performs a fold/aggregation on stream elements, combining them into a single result.
Overload 1 — with identity (always returns T):
int sum = [Link](1, 5).reduce(0, Integer::sum); // 0+1+2+3+4+5 =
15
int product = [Link](1,2,3,4).reduce(1, (a, b) -> a * b); // 24
• identity = initial value (0 for sum, 1 for product)
• Returns T directly (not Optional) — safe because identity guarantees a result
Overload 2 — without identity (returns Optional<T>):
Optional<Integer> max = [Link](3, 1, 4, 1, 5, 9).reduce(Integer::max);
[Link]([Link]::println); // 9
• Returns Optional because stream may be empty
Overload 3 — combiner (for parallel streams with type change):
int totalLength = [Link]('Hello', 'World', 'Java')
.reduce(0,
(sum, s) -> sum + [Link](), // accumulator: Integer + String →
Integer
Integer::sum); // combiner: merges partial results in
parallel
// 5 + 5 + 4 = 14
Common reduce examples:
// Sum of salary of all employees
int totalSalary = [Link]()
.mapToInt(Employee::getSalary).sum(); // better: use primitive stream
// Concatenate strings
String sentence = [Link]().reduce('', (a, b) -> a + ' ' + b).trim();
🔁 Follow-up: When should you prefer reduce() over collect()?
– reduce: for single-value results (sum, max, product). collect: for building collections/maps.
Q5: Explain Optional<T>. How do you use it correctly?
Answer:
Optional<T> is a container that may or may not contain a non-null value. It forces the caller to handle the
'absent' case explicitly, replacing null checks.
Creating Optional:
Optional<String> present = [Link]('hello'); // throws NPE if null
Optional<String> maybe = [Link](value); // null-safe
Optional<String> empty = [Link]();
Checking and getting value:
[Link]() // true if value exists
[Link]() // true if empty (Java 11+)
[Link]() // returns value — throws NoSuchElementException if empty
(avoid!)
Safe retrieval — use these instead of get():
String result = [Link]('default'); // value or
default
String result = [Link](() -> computeDefault()); // lazy default
String result = [Link](() -> new NotFoundException()); // or throw
Transforming Optional:
Optional<Integer> len = [Link](String::length); // transform if present
Optional<String> up = [Link](s -> [Link]() > 3).map(String::toUpperCase);
Optional<String> flat = [Link](s -> [Link]([Link]())); //
avoid nested Optionals
Consuming if present:
[Link](s -> [Link](s));
[Link]( // Java 9+
s -> [Link]('Found: ' + s),
() -> [Link]('Not found'));
Real Spring Boot usage:
public UserDto getUser(Long id) {
return [Link](id) // returns Optional<User>
.map(UserMapper::toDto) // transform to DTO if present
.orElseThrow(() -> new UserNotFoundException(id)); // throw if absent
}
What NOT to do with Optional:
• Don't use as method parameter: void save(Optional<User> user) — use overloads instead
• Don't use as field in entity/bean — not Serializable
• Don't call [Link]() without checking isPresent() first
• Don't wrap collections: Optional<List<T>> — just return empty list
🔁 Follow-up: What is the difference between orElse() and orElseGet()?
– orElse(value): value is ALWAYS computed even if Optional is present (eager)
– orElseGet(supplier): supplier called ONLY if Optional is empty (lazy — prefer for expensive defaults)
Q6: What are Parallel Streams? When should you use them — and when should you NOT?
Answer:
Parallel streams split the data into chunks and process them concurrently using the ForkJoinPool
(common pool — default threads = number of CPU cores).
Creating parallel streams:
[Link]() // directly parallel
[Link]().parallel() // convert existing to parallel
[Link]().sequential() // convert back to sequential
When parallel streams HELP (use them):
• Large data sets (>10,000 elements) where splitting pays off
• CPU-intensive operations per element (heavy computation)
• No shared mutable state — operations are stateless and independent
• Order doesn't matter (or you use forEachOrdered())
long count = [Link]()
.filter(n -> isPrime(n)) // CPU-heavy per element
.count();
When parallel streams HURT (avoid them):
• Small data sets — thread coordination overhead exceeds benefit
• I/O-bound operations (file/network/DB) — threads block, no parallelism gain
• Operations with shared mutable state — race conditions!
• Ordered operations like findFirst() — may need sequential fallback
• When using non-thread-safe collections like ArrayList as target
Thread safety pitfall:
// WRONG — race condition! ArrayList is not thread-safe
List<Integer> result = new ArrayList<>();
[Link]().forEach(result::add); // data corruption!
// CORRECT — use thread-safe collector
List<Integer> result = [Link]().collect([Link]()); // safe
Custom thread pool for parallel streams:
ForkJoinPool pool = new ForkJoinPool(4);
[Link](() -> [Link]().forEach(process)).get();
🔁 Follow-up: What thread pool does parallel stream use? ([Link] — shared across app)
🔁 Follow-up: How do you check if a stream is parallel? ([Link]())
💡 Tip: Most interviews want you to say: 'Parallel streams are NOT always faster — measure first.'
SECTION 4: Stream Scenario / Coding Questions
Q7: SCENARIO: Given a list of employees, solve these stream problems.
Setup:
class Employee {
Long id; String name; String department; int salary; int age;
}
List<Employee> employees = /* list of employees */;
1. Get names of employees with salary > 50000, sorted alphabetically:
List<String> names = [Link]()
.filter(e -> [Link]() > 50000)
.sorted([Link](Employee::getName))
.map(Employee::getName)
.collect([Link]());
2. Find highest paid employee:
Optional<Employee> highest = [Link]()
.max([Link](Employee::getSalary));
[Link](e -> [Link]([Link]()));
3. Average salary by department:
Map<String, Double> avgByDept = [Link]()
.collect([Link](Employee::getDepartment,
[Link](Employee::getSalary)));
4. Count employees in each department:
Map<String, Long> countByDept = [Link]()
.collect([Link](Employee::getDepartment,
[Link]()));
5. Get total salary bill:
int totalSalary = [Link]()
.mapToInt(Employee::getSalary).sum();
6. Partition employees into senior (age > 30) and junior:
Map<Boolean, List<Employee>> partitioned = [Link]()
.collect([Link](e -> [Link]() > 30));
7. Find second highest salary:
Optional<Integer> secondHighest = [Link]()
.map(Employee::getSalary)
.distinct()
.sorted([Link]())
.skip(1)
.findFirst();
8. Get employees grouped by dept, sorted by salary within each dept:
Map<String, List<Employee>> grouped = [Link]()
.collect([Link](Employee::getDepartment,
[Link](
[Link](),
list -> [Link]()
.sorted([Link](Employee::getSalary).reversed())
.collect([Link]()))));
💡 Tip: Solve these on paper before the interview — interviewers often give 1-2 of these live.
Q8: SCENARIO: Common string and number stream problems.
1. Count frequency of characters in a string:
Map<Character, Long> freq = 'hello world'.chars()
.mapToObj(c -> (char) c)
.collect([Link](c -> c, [Link]()));
2. Find duplicate elements in a list:
Set<Integer> seen = new HashSet<>();
Set<Integer> duplicates = [Link]()
.filter(n -> ) // add returns false if already present
.collect([Link]());
3. Flatten list of lists:
List<Integer> flat = [Link]()
.flatMap(Collection::stream)
.collect([Link]());
4. Sum of all even numbers from 1 to 100:
int sum = [Link](1, 100)
.filter(n -> n % 2 == 0)
.sum(); // 2550
5. Get unique words from a sentence (lowercased):
Set<String> words = [Link]([Link]('\\s+'))
.map(String::toLowerCase)
.collect([Link]());
6. Convert list of strings to comma-separated string:
String csv = [Link]().collect([Link](', '));
7. Check if all numbers in list are positive:
boolean allPositive = [Link]().allMatch(n -> n > 0);
8. Find first element starting with 'A' (or default):
String first = [Link]()
.filter(s -> [Link]('A'))
.findFirst()
.orElse('None found');
PART B : FUNCTIONAL INTERFACES & LAMBDA EXPRESSIONS
SECTION 5: Lambda Expressions & Method References
Q9: What is a Lambda Expression? What is its syntax? How does it relate to Functional
Interfaces?
Answer:
A Lambda expression is a concise, anonymous function that can be passed as an argument or stored as a
variable. It provides an implementation for a Functional Interface.
Syntax:
(parameters) -> expression
(parameters) -> { statements; }
Examples of lambda syntax:
() -> [Link]('hello') // no params
x -> x * x // one param, expression body
(x, y) -> x + y // two params, expression
(String s) -> [Link]() // explicit type
(a, b) -> { int sum = a + b; return sum; } // block body with return
Lambda vs Anonymous Class:
// Anonymous class (pre Java 8)
Runnable r1 = new Runnable() {
@Override public void run() { [Link]('run'); }
};
// Lambda (Java 8+) — same thing, cleaner
Runnable r2 = () -> [Link]('run');
Lambda as argument:
List<String> names = [Link]('Charlie', 'Alice', 'Bob');
[Link]((a, b) -> [Link](b)); // lambda as Comparator
[Link](String::compareTo); // method reference — even
cleaner
[Link](name -> [Link](name)); // lambda as Consumer
[Link]([Link]::println); // method reference
Variable capture in lambdas:
String prefix = 'Hello'; // effectively final
[Link](name -> [Link](prefix + ' ' + name)); // OK
prefix = 'Hi'; // COMPILE ERROR — prefix must be effectively final
🔁 Follow-up: What is an 'effectively final' variable?
– A local variable never reassigned after initialization — treated as final even without the keyword.
🔁 Follow-up: Can lambdas throw checked exceptions?
– Only if the functional interface's method declares the checked exception in its throws clause.
Q10: What are Method References? Explain all four types with examples.
Answer:
Method references are shorthand for lambdas that simply call an existing method. Syntax:
ClassName::methodName
Type 1 — Static Method Reference:
// Lambda: (n) -> [Link](n)
// Method reference: Integer::parseInt
List<Integer> nums =
[Link]().map(Integer::parseInt).collect([Link]());
Type 2 — Instance Method Reference on a specific instance:
// Lambda: (s) -> [Link](s) (printer is a specific object)
// Method reference: printer::print
Printer printer = new Printer();
[Link](printer::print);
Type 3 — Instance Method Reference on arbitrary instance of a type (most common):
// Lambda: (s) -> [Link]() (s is the stream element)
// Method reference: String::toUpperCase
[Link]().map(String::toUpperCase).collect([Link]());
[Link]().filter(String::isEmpty).count();
[Link]().sorted(String::compareTo);
Type 4 — Constructor Reference:
// Lambda: (name) -> new Employee(name)
// Method reference: Employee::new
List<Employee> employees =
[Link]().map(Employee::new).collect([Link]());
Supplier<List<String>> listFactory = ArrayList::new; // Supplier<ArrayList>
🔁 Follow-up: When can you NOT use a method reference and must use a lambda?
– When you need to do more than call one method: (x) -> x > 0 && x < 100
– When you need to manipulate arguments before passing them
SECTION 6: Built-in Functional Interfaces ([Link])
Q11: Explain all the key built-in Functional Interfaces in [Link].
Answer:
Java 8 provides a rich set of functional interfaces in [Link] package.
The 4 core interfaces:
1. Predicate<T> — takes T, returns boolean
Predicate<String> isLong = s -> [Link]() > 5;
[Link]('Hello'); // false
[Link]('HelloWorld'); // true
// Composing predicates:
Predicate<String> isLongAndUpper = [Link](s -> [Link]([Link]()));
Predicate<String> isLongOrEmpty = [Link](String::isEmpty);
Predicate<String> isShort = [Link]();
2. Function<T, R> — takes T, returns R
Function<String, Integer> length = String::length;
[Link]('hello'); // 5
// Composing functions:
Function<String, String> upper = String::toUpperCase;
Function<String, Integer> upperLength = [Link](length); // compose: first
upper, then length
Function<String, Integer> composed = [Link](upper); // reverse: first
upper, then length
3. Consumer<T> — takes T, returns void
Consumer<String> print = [Link]::println;
[Link]('hello');
Consumer<String> printAndLog = [Link](s -> [Link]('Processed: {}', s));
4. Supplier<T> — takes nothing, returns T
Supplier<LocalDate> today = LocalDate::now;
Supplier<List<String>> listMaker = ArrayList::new;
[Link](); // current date
[Link](); // new ArrayList
Bi-variants (two inputs):
BiPredicate<String, Integer> longerThan = (s, n) -> [Link]() > n;
BiFunction<String, String, Integer> compare = String::compareTo;
BiConsumer<String, Integer> printRepeat = (s, n) -> { for(int i=0;i<n;i++)
print(s); };
Operator variants (same type in and out):
UnaryOperator<String> trim = String::trim; // Function<String, String>
BinaryOperator<Integer> sum = Integer::sum; // BiFunction<Int, Int, Int>
BinaryOperator<String> concat = (a, b) -> a + b;
Primitive specializations (avoid boxing):
IntPredicate isEven = n -> n % 2 == 0;
IntFunction<String> toStr = n -> [Link](n);
ToIntFunction<String> len = String::length;
IntUnaryOperator square = n -> n * n;
IntBinaryOperator multiply = (a, b) -> a * b;
🔁 Follow-up: What is the difference between [Link]() and [Link]()?
– andThen(g): apply this, then g → [Link](g) = g(f(x))
– compose(g): apply g first, then this → [Link](g) = f(g(x))
💡 Tip: Know [Link]/or/negate and [Link]/compose — commonly asked with stream
chaining.
Q12: What is @FunctionalInterface? Can you create your own? Scenarios where it is useful.
Answer:
@FunctionalInterface is an annotation that marks an interface as having exactly ONE abstract method.
The compiler enforces this — it's a safeguard.
Rules:
• Exactly ONE abstract method (SAM — Single Abstract Method)
• Can have any number of default, static, and private methods
• Can override Object methods (toString, equals) without counting as abstract
Creating custom functional interfaces:
@FunctionalInterface
public interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
TriFunction<Integer, Integer, Integer, Integer> sum3 = (a, b, c) -> a + b + c;
[Link](1, 2, 3); // 6
@FunctionalInterface
public interface Validator<T> {
boolean validate(T value);
default Validator<T> and(Validator<T> other) {
return val -> [Link](val) && [Link](val);
}
}
Validator<String> notEmpty = s -> ![Link]();
Validator<String> notTooLong = s -> [Link]() <= 100;
Validator<String> nameValidator = [Link](notTooLong);
[Link]('John'); // true
Real use case — Strategy pattern with lambdas:
@FunctionalInterface
interface DiscountStrategy {
double apply(double price);
}
DiscountStrategy noDiscount = price -> price;
DiscountStrategy tenPercent = price -> price * 0.9;
DiscountStrategy halfPrice = price -> price * 0.5;
DiscountStrategy fixedTen = price -> price - 10;
double finalPrice = [Link](100.0); // 90.0
🔁 Follow-up: Is Runnable a functional interface? (Yes — has only one abstract method: run())
🔁 Follow-up: Is Comparator a functional interface? (Yes — has only one abstract method: compare(), others
are default/static)
PART C : GENERICS
SECTION 7: Generics — Type Parameters, Wildcards, Bounded Types
Q13: What are Generics in Java? Why were they introduced? What is Type Erasure?
Answer:
Generics allow you to write type-safe code that works with any type, while catching type errors at compile
time rather than runtime.
Why Generics? — The problem before Java 5:
// Pre-generics — everything stored as Object
List list = new ArrayList();
[Link]('hello');
[Link](42); // no compile error!
String s = (String) [Link](1); // ClassCastException at RUNTIME — very bad
// With Generics — type-safe
List<String> list = new ArrayList<>();
[Link]('hello');
[Link](42); // COMPILE ERROR — caught at compile time
String s = [Link](0); // no cast needed
Generic Class:
public class Box<T> {
private T value;
public Box(T value) { [Link] = value; }
public T getValue() { return value; }
}
Box<String> strBox = new Box<>('hello');
Box<Integer> intBox = new Box<>(42);
Generic Method:
public <T extends Comparable<T>> T max(T a, T b) {
return [Link](b) > 0 ? a : b;
}
max('apple', 'banana'); // 'banana'
max(10, 20); // 20
Type Erasure — how generics work at runtime:
Java implements generics via type erasure. Generic type information is REMOVED at compile time and
replaced with Object (or bound type). At runtime, there is NO generic type info.
// At compile time: List<String>
// At runtime (bytecode): List (raw type, elements are Object)
Consequences of type erasure:
• Cannot do: new T() — T is unknown at runtime
• Cannot do: new T[10] — generic arrays not allowed
• Cannot do: instanceof check with generic type: obj instanceof List<String> // compile error
• Cannot do: catch (T e) — generic exception types not allowed
🔁 Follow-up: What is a raw type? Why should you avoid it?
– List instead of List<String> is a raw type. Skips type checking — can cause ClassCastException at
runtime.
🔁 Follow-up: Can you create a generic array? (No — new T[10] is not allowed due to type erasure)
Q14: What are Wildcards in Generics? Explain <?>、<? extends T> and <? super T> — the
PECS rule.
Answer:
Wildcards (?) allow flexibility when the exact type is unknown or when working with type hierarchies.
1. Unbounded Wildcard <?> — 'any type':
public void printList(List<?> list) {
for (Object o : list) [Link](o); // can only read as Object
}
printList(new ArrayList<String>()); // works
printList(new ArrayList<Integer>()); // works
Use: when you only need to READ and don't care about element type.
2. Upper Bounded Wildcard <? extends T> — 'T or any subtype':
public double sumList(List<? extends Number> list) {
double sum = 0;
for (Number n : list) sum += [Link](); // can READ as Number
// [Link](3.14); // COMPILE ERROR — can't add (type unknown)
return sum;
}
sumList(new ArrayList<Integer>()); // OK — Integer extends Number
sumList(new ArrayList<Double>()); // OK — Double extends Number
Use: when you only PRODUCE/READ values from the collection (covariant).
3. Lower Bounded Wildcard <? super T> — 'T or any supertype':
public void addNumbers(List<? super Integer> list) {
[Link](1); // CAN add Integer
[Link](2); // CAN add Integer
// Integer n = [Link](0); // COMPILE ERROR — reading gives Object
}
addNumbers(new ArrayList<Integer>()); // OK
addNumbers(new ArrayList<Number>()); // OK — Number is super of Integer
addNumbers(new ArrayList<Object>()); // OK — Object is super of Integer
Use: when you only CONSUME/WRITE values into the collection (contravariant).
THE PECS RULE — Producer Extends, Consumer Super:
• If a collection PRODUCES elements for you to read → use <? extends T>
• If a collection CONSUMES elements you supply → use <? super T>
• If both read and write → use exact type <T>
// [Link] uses PECS perfectly:
public static <T> void copy(List<? super T> dest, List<? extends T> src)
// src PRODUCES — extends. dest CONSUMES — super.
🔁 Follow-up: Why can't you add to a List<? extends Number>?
– Compiler doesn't know the exact type — it could be List<Integer> or List<Double>. Adding an Integer
to List<Double> would be wrong.
🔁 Follow-up: What is the difference between List<?> and List<Object>?
– List<Object>: can only hold List<Object> exactly. List<?>: accepts List<String>, List<Integer>, any List.
💡 Tip: PECS is a highly asked concept at 3+ year level. Practice with [Link]() as the classic example.
Q15: What are Bounded Type Parameters? What is the difference between <T extends
Comparable<T>> and <T extends Number>?
Answer:
Bounded type parameters restrict what types can be used as a generic parameter.
Upper bound — T extends SomeClass:
Restricts T to SomeClass or its subclasses.
public <T extends Number> double sum(List<T> list) {
return [Link]().mapToDouble(Number::doubleValue).sum();
}
sum([Link](1, 2, 3)); // OK — Integer extends Number
sum([Link](1.5, 2.5)); // OK — Double extends Number
sum([Link]('a', 'b')); // COMPILE ERROR — String doesn't extend Number
Multiple bounds — T extends A & B:
public <T extends Comparable<T> & Serializable> T maxSerialized(T a, T b) {
return [Link](b) >= 0 ? a : b;
}
• Class bound must come FIRST (before interface bounds)
• T extends Number & Comparable<T> & Serializable — valid
• T extends Comparable<T> & Number — valid (interface first is fine)
Bounded wildcard vs bounded type parameter:
// <T extends Number> — use when T is referenced multiple times
public <T extends Number> void copy(List<T> src, List<T> dest) { ... }
// <? extends Number> — use when T is referenced only once (simpler)
public double sum(List<? extends Number> list) { ... }
Recursive/self-referential bound — very common:
// T must be comparable to itself — used in sort algorithms
public <T extends Comparable<T>> T max(T a, T b) {
return [Link](b) >= 0 ? a : b;
}
🔁 Follow-up: What is the difference between <T> and <?> in generics?
– <T> is a named type parameter (can reference T elsewhere in the method/class)
– <?> is an anonymous wildcard (can't reference the type again)
Q16: SCENARIO: Why does this code not compile? Generic pitfalls.
Pitfall 1 — Generic array creation:
T[] array = new T[10]; // COMPILE ERROR — type erasure makes this impossible
// FIX: use List<T> or pass Class<T> and use [Link]
@SuppressWarnings('unchecked') T[] arr = (T[]) new Object[10]; // ugly
workaround
Pitfall 2 — Overloading with generics:
void process(List<String> list) { }
void process(List<Integer> list) { } // COMPILE ERROR — same erasure: List
// Both erase to process(List) — duplicate method after erasure
Pitfall 3 — Static members and generics:
class Box<T> {
static T defaultValue; // COMPILE ERROR — T not available in static context
}
Pitfall 4 — instanceof with generics:
if (list instanceof List<String>) { } // COMPILE ERROR — type info erased at
runtime
if (list instanceof List<?>) { } // OK — unbounded wildcard allowed
Pitfall 5 — Heap pollution with varargs:
@SafeVarargs // suppresses unchecked warning — only use if safe
public final <T> void safeMethod(T... items) { ... }
Pitfall 6 — Raw type in collection:
List list = new ArrayList<String>(); // raw type
[Link](42); // compiles with warning — raw type bypasses type check
String s = (String) [Link](0); // ClassCastException — heap pollution
💡 Tip: Type erasure is the root cause of most generic pitfalls. Always ask: 'what does this look like at runtime?'
QUICK REFERENCE: STREAMS + GENERICS + FUNCTIONAL
INTERFACES
Concept Key Points
Stream pipeline Source → Intermediate (lazy) → Terminal (triggers execution)
filter / map / flatMap filter: keep/remove. map: transform 1:1. flatMap: transform +
flatten.
collect() Most powerful terminal op — toList, toSet, toMap, groupingBy,
joining
groupingBy Map<K, List<T>>. groupingBy(key, downstream) for
aggregation.
partitioningBy Map<Boolean, List<T>> — split into true/false groups.
reduce() Fold stream to single value. With identity returns T; without
returns Optional.
Optional orElse (eager), orElseGet (lazy), orElseThrow, map, filter,
ifPresent.
Parallel stream Use for large CPU-heavy data. Avoid for small/IO/mutable-state
ops.
Predicate<T> test(T) → boolean. Compose: and(), or(), negate().
Function<T,R> apply(T) → R. Compose: andThen(after), compose(before).
Consumer<T> accept(T) → void. Chain: andThen().
Supplier<T> get() → T. No input. Use for lazy default values.
UnaryOperator<T> Function<T,T> — same in and out type. e.g. String::trim.
BinaryOperator<T> BiFunction<T,T,T> — two same-type inputs, same-type output.
Method refs — 4 types Static: Class::static. Instance(specific): obj::method.
Instance(arbitrary): Type::method. Constructor: Class::new.
@FunctionalInterface Exactly 1 abstract method. Can have default/static/private
methods.
Generics — type erasure Generic type info removed at runtime. T becomes Object in
bytecode.
<T extends X> Upper bound — T must be X or subtype. Can READ as X.
<? extends T> Wildcard upper bound — PRODUCER. Read only, cannot add.
<? super T> Wildcard lower bound — CONSUMER. Can add T, read as
Object.
PECS rule Producer Extends, Consumer Super. Classic:
[Link](dest<super T>, src<extends T>)
Streams + Generics + Functional Interfaces — All Done! 🚀
Spring Boot ✅ Collections ✅ OOPs ✅ Exceptions ✅ Streams + Generics + FI ✅
JAVA BASICS • MULTITHREADING
Final Interview Preparation Guide — Complete Java Stack
Custom Software Engineer | Accenture | 3+ Years Experience Level
PART A : JAVA BASICS
SECTION 1: Data Types, Memory Model & String
Q1: What are primitive data types in Java? What is the difference between stack and heap
memory?
Primitive Data Types (8 total):
• byte — 8-bit signed integer. Range: -128 to 127. Default: 0
• short — 16-bit signed integer. Range: -32,768 to 32,767. Default: 0
• int — 32-bit signed integer. Range: ~-2 billion to 2 billion. Default: 0
• long — 64-bit signed integer. Literal suffix: 100L. Default: 0L
• float — 32-bit IEEE 754 floating point. Literal suffix: 3.14f. Default: 0.0f
• double — 64-bit IEEE 754 floating point. Default: 0.0d
• char — 16-bit Unicode character. Range: 0 to 65,535. Default: '\u0000'
• boolean — true or false. Default: false
Wrapper Classes (autoboxing/unboxing):
int i = 5;
Integer boxed = i; // autoboxing — int → Integer
int unboxed = boxed; // unboxing — Integer → int
Integer a = 127; Integer b = 127; a == b → true (cached -128 to 127)
Integer x = 128; Integer y = 128; x == y → false (new objects above 127)
Stack vs Heap Memory:
• Stack — stores: local variables, method call frames, references (pointers), primitive values
– LIFO structure, fast allocation/deallocation
– Each thread has its OWN stack
– Fixed size — StackOverflowError if exceeded
• Heap — stores: all objects (new keyword), instance variables, arrays
– Shared across ALL threads
– Managed by Garbage Collector
– OutOfMemoryError if full
void method() {
int x = 10; // x stored on STACK
String s = new String(); // reference 's' on STACK, String object on HEAP
}
🔁 Follow-up: What is Integer caching? Why does [Link](127)==[Link](127) return true?
– Java caches Integer objects from -128 to 127. valueOf() returns cached instance; 'new Integer()' always
creates new object.
🔁 Follow-up: What is autoboxing? Can it cause performance issues?
– Yes — in tight loops, boxing/unboxing creates many short-lived objects. Use primitive streams
(IntStream) to avoid.
Q2: Explain String, StringBuilder, and StringBuffer. Why is String immutable?
String — immutable, stored in String Pool:
String s = 'hello';
s = s + ' world'; // creates a NEW String object, old 'hello' stays in pool
• Every concatenation with + creates a new object — very inefficient in loops
• Immutable because: thread-safe, String Pool possible, hashCode cacheable, security (passwords,
URLs)
StringBuilder — mutable, NOT thread-safe, FAST:
StringBuilder sb = new StringBuilder();
[Link]('Hello').append(' ').append('World'); // method chaining
[Link](5, ','); // insert at index
[Link](0, 5); // delete range
[Link](); // reverse
[Link](); // convert to String
• Internal char[] grows as needed (default capacity 16)
• Use in single-threaded string building — loops, parsers
StringBuffer — mutable, thread-safe (synchronized), SLOWER:
• Same API as StringBuilder but every method is synchronized
• Use only when string is shared across threads (rare)
Performance comparison for 10,000 concatenations:
• String + operator: O(n²) — creates 10,000 temporary objects
• StringBuilder: O(n) — single buffer, minimal allocation
// WRONG in loops
String result = '';
for (String s : list) result += s; // 10k objects created!
// CORRECT
StringBuilder sb = new StringBuilder();
for (String s : list) [Link](s);
String result = [Link]();
🔁 Follow-up: What does [Link]() do? (Adds string to pool; returns pooled reference)
🔁 Follow-up: Is String thread-safe? (Yes — because it's immutable, no thread can change it)
SECTION 2: Key Java Concepts — instanceof, var, equals, hashCode
Q3: Explain: instanceof, var (Java 10), == vs equals(), and pass-by-value vs pass-by-reference.
instanceof — runtime type check:
Animal a = new Dog();
a instanceof Dog // true
a instanceof Animal // true
a instanceof Cat // false
// Java 16+ pattern matching:
if (a instanceof Dog d) { [Link](); } // no explicit cast needed
var — local variable type inference (Java 10+):
var list = new ArrayList<String>(); // inferred as ArrayList<String>
var map = new HashMap<String, Integer>();
var num = 42; // inferred as int
• var is NOT dynamic typing — type is fixed at compile time
• Only for local variables — NOT for fields, parameters, or return types
• Improves readability for verbose generic types
== vs equals():
String a = new String('hello');
String b = new String('hello');
a == b // false — different objects (reference comparison)
[Link](b) // true — same content (logical comparison)
• == compares memory addresses (references)
• equals() compares content — override for meaningful equality
• For primitives: == always compares values
Java is ALWAYS pass-by-value:
// Primitive — copy of value passed
void increment(int x) { x++; } // original not changed
// Object — copy of REFERENCE passed
void addItem(List<String> list) { [Link]('hello'); } // modifies same object!
void reassign(List<String> list) { list = new ArrayList<>(); } // caller
unaffected
Java passes the VALUE of the reference — not the reference itself. The method gets a copy of the pointer,
not the pointer's pointer.
🔁 Follow-up: Can you use var with lambda expressions? (No — lambda needs a target type which var can't
provide)
SECTION 3: Java 8+ Important Features (Quick Reference)
Q4: What are the most important Java 8+ features? Summarise each.
Java 8 (2014) — The biggest release:
• Lambda expressions — concise anonymous functions: () -> expression
• Stream API — functional pipeline for collections processing
• Optional<T> — null-safe container, avoids NullPointerException
• Default & static methods in interfaces — backward-compatible interface evolution
• Functional Interfaces (@FunctionalInterface)
• Method references (Class::method)
• New Date/Time API — [Link] (LocalDate, LocalDateTime, ZonedDateTime, Duration)
LocalDate today = [Link]();
LocalDate dob = [Link](1995, [Link], 15);
long age = [Link](dob, today);
Java 9 (2017):
• Module system (JPMS) — strong encapsulation at the JAR level
• Collection factory methods: [Link](), [Link](), [Link]()
• Stream additions: takeWhile(), dropWhile(), iterate() with predicate
• Optional: ifPresentOrElse(), or(), stream()
• Private methods in interfaces
Java 10 (2018):
• var — local variable type inference
• [Link](), [Link](), [Link]()
Java 11 (2018 — LTS):
• String methods: isBlank(), strip(), lines(), repeat(n)
' hello '.strip() // 'hello' (Unicode-aware, unlike trim())
'ha'.repeat(3) // 'hahaha'
''.isBlank() // true
• [Link]() / [Link]()
• var in lambda parameters: (var x, var y) -> x + y
Java 14 (2020):
• Switch Expressions (standard): int day = switch(d) { case MON -> 1; ... }
• Helpful NullPointerException messages
Java 16 (2021):
• Records — immutable data classes (replaces Lombok @Data for simple cases)
record Point(int x, int y) {} // auto-generates: constructor, getters, equals,
hashCode, toString
Point p = new Point(3, 4); p.x(); p.y();
• Pattern Matching instanceof: if (obj instanceof String s) { [Link](); }
Java 17 (2021 — LTS):
• Sealed classes — restrict which classes can extend/implement
sealed interface Shape permits Circle, Rectangle, Triangle {}
• Pattern Matching in switch (preview)
🔁 Follow-up: What is the difference between [Link]() and [Link]()? (strip() is Unicode-aware, handles
all whitespace characters; trim() only handles ASCII space)
💡 Tip: Know Java 8 features deeply + Java 11/17 LTS features — these are the versions used in production at
Accenture.
PART B : MULTITHREADING & CONCURRENCY
SECTION 4: Thread Basics — Creating Threads, Lifecycle
Q5: What is a Thread? How do you create threads in Java? Explain the Thread lifecycle.
What is a Thread?
A thread is the smallest unit of CPU execution. Java is multi-threaded — multiple threads run concurrently,
sharing the process heap but each having its own stack.
3 ways to create a thread:
1. Extend Thread class:
class MyThread extends Thread {
@Override public void run() {
[Link]('Thread: ' + [Link]().getName());
}
}
new MyThread().start(); // start() — NOT run() directly!
2. Implement Runnable interface (PREFERRED — more flexible):
Runnable task = () -> [Link]('Running in: ' +
[Link]().getName());
Thread t = new Thread(task, 'MyThread');
[Link]();
3. Implement Callable (returns result, can throw checked exception):
Callable<Integer> task = () -> { return 42; };
ExecutorService executor = [Link]();
Future<Integer> future = [Link](task);
Integer result = [Link](); // blocks until result is ready
[Link]();
Why Runnable over Thread extension?
• Java is single-inheritance — extending Thread wastes your inheritance slot
• Separates task (what to do) from mechanism (how to run) — better design
• Runnable/Callable can be submitted to ExecutorService
Thread Lifecycle — 6 states:
• NEW — thread created but start() not yet called
• RUNNABLE — start() called; running or ready to run (waiting for CPU)
• BLOCKED — waiting to acquire a synchronized lock
• WAITING — waiting indefinitely (wait(), join() with no timeout)
• TIMED_WAITING — waiting for specified time (sleep(ms), wait(ms), join(ms))
• TERMINATED — run() completed or exception thrown
[Link]().getState() // get current state
🔁 Follow-up: What is the difference between start() and run()?
– start(): creates new thread, calls run() in that thread
– run(): just a normal method call in the CURRENT thread — no new thread created!
🔁 Follow-up: What is [Link]() vs [Link]()?
– sleep(): pauses current thread for ms, does NOT release lock
– wait(): pauses and RELEASES the monitor lock (must be in synchronized block)
SECTION 5: Synchronization & Thread Safety
Q6: What is synchronization in Java? Explain synchronized keyword, locks, and the problems
it solves.
Why synchronization?
When multiple threads access shared mutable data, race conditions occur — threads interleave in
unexpected ways causing data corruption.
Race condition example:
class Counter {
int count = 0;
void increment() { count++; } // NOT atomic: read → increment → write
}
// 2 threads call increment() 1000 times each — result may be less than 2000!
synchronized keyword — mutual exclusion lock (monitor):
1. Synchronized method:
class Counter {
int count = 0;
synchronized void increment() { count++; } // only one thread at a time
synchronized int getCount() { return count; }
}
2. Synchronized block (finer-grained — preferred):
class Counter {
int count = 0;
Object lock = new Object();
void increment() {
synchronized(lock) { // lock on specific object
count++; // only critical section is locked
}
}
}
3. Static synchronized method — locks on Class object:
static synchronized void classLevelOp() { } // locks [Link]
volatile keyword — visibility guarantee:
• Ensures a variable's value is read from and written to MAIN MEMORY (not CPU cache)
• Guarantees visibility across threads but NOT atomicity
volatile boolean running = true; // change visible to all threads immediately
// Thread 1: running = false;
// Thread 2: while (running) { ... } — sees update without sync
volatile vs synchronized:
• volatile: visibility only. No mutual exclusion. No compound atomicity (i++ is NOT safe with volatile
alone)
• synchronized: visibility + mutual exclusion + atomicity
🔁 Follow-up: What are the three thread-safety problems?
– Race condition: multiple threads read/write shared data concurrently
– Visibility: changes by one thread not visible to others (CPU cache)
– Atomicity: compound operations (check-then-act, read-modify-write) not atomic
🔁 Follow-up: What is a deadlock? Give an example.
– Two threads each hold a lock the other needs → both wait forever
💡 Tip: Mention that synchronized is coarse-grained and [Link] offers finer control.
Q7: What is deadlock? What are the four conditions? How do you prevent it?
What is Deadlock?
A deadlock occurs when two or more threads are blocked forever, each waiting for a lock held by the
other.
Classic deadlock example:
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(() -> {
synchronized(lockA) { // T1 acquires lockA
[Link](50);
synchronized(lockB) { } // T1 waits for lockB — held by T2
}
});
Thread t2 = new Thread(() -> {
synchronized(lockB) { // T2 acquires lockB
[Link](50);
synchronized(lockA) { } // T2 waits for lockA — held by T1
}
});
// T1 and T2 wait forever — DEADLOCK
Four Coffman Conditions for Deadlock:
• Mutual Exclusion — resources cannot be shared (only one thread holds the lock)
• Hold and Wait — thread holds one lock while waiting for another
• No Preemption — locks cannot be forcibly taken away
• Circular Wait — T1 waits for T2, T2 waits for T1 (cycle)
Prevention strategies:
• Lock ordering — always acquire locks in the SAME ORDER across all threads
// ALWAYS lock lockA before lockB — breaks circular wait
• tryLock() with timeout — use [Link](timeout) — back off if lock not acquired
if ([Link](100, [Link])) { ... }
• Avoid nested locks — minimize holding multiple locks simultaneously
• Lock-free data structures — use Atomic classes, ConcurrentHashMap
🔁 Follow-up: What is livelock? (Threads keep responding to each other's state but make no progress — like
two people in a corridor both stepping aside for each other)
🔁 Follow-up: What is starvation? (A thread never gets CPU/lock because other threads keep getting priority)
SECTION 6: ExecutorService, Thread Pools & Callable/Future
Q8: What is ExecutorService? Explain thread pools and the types of executors.
Why ExecutorService?
Creating a new Thread for every task is expensive (thread creation overhead) and uncontrolled (too many
threads = OutOfMemoryError). ExecutorService manages a pool of reusable threads.
Core API:
ExecutorService executor = [Link](4);
[Link](runnable); // fire-and-forget (Runnable)
Future<T> f = [Link](callable); // submit task, get Future
[Link](); // graceful: finish submitted tasks,
then stop
[Link](); // interrupt running tasks immediately
Types of thread pools:
• newFixedThreadPool(n) — fixed number of threads. Excess tasks queue. Good for CPU-bound
tasks.
ExecutorService pool =
[Link]([Link]().availableProcessors());
• newCachedThreadPool() — creates threads on demand, reuses idle ones, cleans up after 60s. Good
for short-lived async tasks.
• newSingleThreadExecutor() — single thread, tasks queued sequentially. Good for ordered execution.
• newScheduledThreadPool(n) — run tasks at delay or periodically.
ScheduledExecutorService sched = [Link](2);
[Link](task, 0, 5, [Link]); // every 5 seconds
• newVirtualThreadPerTaskExecutor() — Java 21 virtual threads (lightweight)
Callable + Future — get results from threads:
ExecutorService pool = [Link](3);
List<Callable<Integer>> tasks = [Link](
() -> compute(1), () -> compute(2), () -> compute(3));
List<Future<Integer>> futures = [Link](tasks); // submit all
for (Future<Integer> f : futures) {
[Link]([Link]()); // blocks until result ready
}
[Link]();
Future methods:
• get() — blocks until result; throws ExecutionException if task threw
• get(timeout, unit) — blocks with timeout; throws TimeoutException
• isDone() — true if completed (normally, cancelled, or with exception)
• cancel(mayInterrupt) — attempt to cancel
🔁 Follow-up: What is CompletableFuture? How is it better than Future?
– CompletableFuture supports non-blocking callbacks (.thenApply, .thenCompose, .thenAccept) and
chaining without blocking get()
[Link](() -> fetchUser(id))
.thenApply(user -> enrichWithOrders(user))
.thenAccept(result -> sendResponse(result));
🔁 Follow-up: What is the difference between execute() and submit()? (execute: Runnable, no result; submit:
Runnable/Callable, returns Future)
SECTION 7: ReentrantLock, Atomic Classes & wait/notify
Q9: What is ReentrantLock? How is it better than synchronized? Explain Atomic classes.
ReentrantLock ([Link]):
A more flexible alternative to synchronized. 'Reentrant' means the same thread can acquire it multiple
times without deadlocking itself.
ReentrantLock lock = new ReentrantLock();
void transfer(Account from, Account to, double amount) {
[Link](); // acquire lock explicitly
try {
[Link](amount);
[Link](amount);
} finally {
[Link](); // MUST release in finally — always!
}
}
ReentrantLock advantages over synchronized:
• tryLock() — try to acquire without blocking (deadlock prevention):
if ([Link](100, [Link])) { try { ... } finally
{ [Link](); } }
• lockInterruptibly() — thread can be interrupted while waiting for lock
• fairness — new ReentrantLock(true) → longest-waiting thread gets lock first
• Multiple Condition objects — finer wait/signal control
Condition notFull = [Link]();
Condition notEmpty = [Link]();
• Can check lock status: [Link](), [Link]()
ReadWriteLock — readers don't block each other:
ReadWriteLock rwLock = new ReentrantReadWriteLock();
[Link]().lock(); // multiple readers OK simultaneously
[Link]().lock(); // exclusive — blocks all readers and writers
Atomic Classes ([Link]) — lock-free thread safety:
Use Compare-And-Swap (CAS) CPU instruction — no locking overhead.
AtomicInteger counter = new AtomicInteger(0);
[Link](); // atomic i++ — thread-safe, no sync
[Link](5); // atomic add
[Link](5, 10); // CAS: if current==5, set to 10
[Link](); // read current value
AtomicLong atomicLong = new AtomicLong();
AtomicBoolean atomicBool = new AtomicBoolean(false);
AtomicReference<String> ref = new AtomicReference<>('initial');
When to use:
• Simple counters/flags → AtomicInteger/AtomicBoolean (fastest)
• Need fairness / interruptibility / try-lock → ReentrantLock
• Read-heavy data → ReadWriteLock
• Collection → ConcurrentHashMap, CopyOnWriteArrayList
🔁 Follow-up: What is CAS (Compare-And-Swap)?
– CPU-level atomic instruction: compare memory value with expected; if match, swap to new value; if not,
retry. No OS-level locking.
🔁 Follow-up: What is AtomicReference used for? (Thread-safe updates to object references)
Q10: Explain wait(), notify(), and notifyAll(). What is the Producer-Consumer pattern?
wait() / notify() / notifyAll() — inter-thread communication:
These methods are on Object class and MUST be called inside a synchronized block.
• wait() — releases the lock and puts thread in WAITING state until notified
• notify() — wakes up ONE random waiting thread on this object's monitor
• notifyAll() — wakes up ALL waiting threads (prefer this — avoids missed signals)
Producer-Consumer pattern:
class SharedBuffer {
private Queue<Integer> queue = new LinkedList<>();
private final int CAPACITY = 5;
public synchronized void produce(int item) throws InterruptedException {
while ([Link]() == CAPACITY) {
wait(); // buffer full — wait for consumer
}
[Link](item);
[Link]('Produced: ' + item);
notifyAll(); // wake up waiting consumers
}
public synchronized int consume() throws InterruptedException {
while ([Link]()) {
wait(); // buffer empty — wait for producer
}
int item = [Link]();
[Link]('Consumed: ' + item);
notifyAll(); // wake up waiting producers
return item;
}
}
Why while loop instead of if for wait()?
Spurious wakeups — threads can wake up without being notified. The while loop re-checks the condition
after waking.
Modern alternative — BlockingQueue:
BlockingQueue<Integer> bq = new ArrayBlockingQueue<>(5);
// Producer:
[Link](item); // blocks if full — no manual sync/wait/notify needed
// Consumer:
int item = [Link](); // blocks if empty — clean and thread-safe
🔁 Follow-up: Why must wait/notify be in synchronized block?
– They operate on the object's monitor. Calling outside synchronized throws
IllegalMonitorStateException.
🔁 Follow-up: notify() vs notifyAll()? (notify: wakes one random thread — risk of wrong thread waking; notifyAll:
safe, wakes all)
SECTION 8: Thread-Local, CountDownLatch, Semaphore & Scenario
Questions
Q11: What is ThreadLocal? What is CountDownLatch? What is Semaphore?
ThreadLocal<T> — per-thread variable storage:
Each thread that accesses a ThreadLocal gets its OWN copy of the value. Threads don't share it.
ThreadLocal<SimpleDateFormat> dateFormat =
[Link](() -> new SimpleDateFormat('yyyy-MM-dd'));
// Each thread gets its own SimpleDateFormat — no synchronization needed
String formatted = [Link]().format(new Date());
[Link](); // IMPORTANT: call in finally to avoid memory leaks
Use cases: per-thread database connections, user context/session in web requests (Spring's
RequestContextHolder uses ThreadLocal internally).
Warning: Always call remove() when done — especially in thread pools where threads are reused.
CountDownLatch — wait for N tasks to complete:
int taskCount = 3;
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
[Link](() -> {
try { doWork(); }
finally { [Link](); } // decrement count
});
}
[Link](); // main thread BLOCKS until count reaches 0
[Link]('All tasks done!');
• One-time use — cannot be reset
• Use case: parallel service calls, application startup wait
CyclicBarrier — all threads wait at barrier, then proceed together:
CyclicBarrier barrier = new CyclicBarrier(3, () -> [Link]('All at
barrier!'));
// Each thread calls [Link]() — all 3 must arrive before any proceeds
• Reusable — can be reset for next cycle
Semaphore — control access with permits (N threads simultaneously):
Semaphore semaphore = new Semaphore(3); // max 3 threads at once
[Link](); // get a permit (blocks if none available)
try { accessSharedResource(); }
finally { [Link](); } // return permit
Use cases: rate limiting, connection pool management, throttling concurrent access.
🔁 Follow-up: What is the difference between CountDownLatch and CyclicBarrier?
– CountDownLatch: count-down to zero, tasks can finish at different times; one-time use
– CyclicBarrier: all threads must reach the barrier simultaneously; reusable
Q12: SCENARIO: Common multithreading interview problems — what is wrong and how to fix?
Scenario 1 — Lazy Singleton with race condition:
// BROKEN — race condition in multithreaded environment
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // Thread A and B both see null
instance = new Singleton(); // BOTH create instances!
}
return instance;
}
}
// FIX — Double-checked locking with volatile
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized ([Link]) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
Scenario 2 — Incorrect use of sleep() in synchronized block:
// BAD — sleep() holds the lock for 5 seconds — blocks everyone!
synchronized void process() {
[Link](5000); // holds lock while sleeping
doWork();
}
// BETTER — only synchronize the critical section
void process() {
[Link](5000); // sleep OUTSIDE sync — doesn't hold lock
synchronized(this) { doWork(); }
}
Scenario 3 — Calling run() instead of start():
Thread t = new Thread(() ->
[Link]([Link]().getName()));
[Link](); // WRONG — runs in main thread! Prints 'main'
[Link](); // CORRECT — starts new thread! Prints 'Thread-0'
Scenario 4 — Not shutting down ExecutorService:
// BAD — JVM never exits! Non-daemon threads keep JVM alive
ExecutorService pool = [Link](4);
[Link](task);
// forgot: [Link]()
// GOOD
[Link]();
if () {
[Link]();
}
💡 Tip: These scenarios show real production awareness — exactly what Accenture looks for at 3+ years.
FINAL QUICK REFERENCE — Java Basics + Multithreading
Concept Key Points to Remember
Primitives (8) byte, short, int, long, float, double, char, boolean
Integer Cache -128 to 127 cached. == works. Above 127: new object, == fails.
Stack vs Heap Stack: local vars, refs, frames (per thread). Heap: all objects
(shared).
String immutability Immutable for security, thread-safety, pool, hashCode caching.
StringBuilder Mutable, NOT thread-safe, FAST. Use in loops. Default capacity
16.
StringBuffer Mutable, thread-safe (synchronized), SLOW. Rarely needed.
var (Java 10) Local type inference. Type fixed at compile-time. Not for
fields/params.
Java 8 features Lambdas, Streams, Optional, default methods, Date/Time API,
Method refs
Java 16 Records record Point(int x, int y){} — auto
constructor/getters/equals/hashCode
Thread creation Extend Thread (limited) or implement Runnable/Callable
(preferred)
Thread states (6) NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING
→ TERMINATED
start() vs run() start(): new thread. run(): same thread (just method call — NOT
multithreaded)
synchronized Method or block. Mutual exclusion + visibility + atomicity.
volatile Visibility only (main memory). NOT atomic. Use for simple flags.
Deadlock conditions Mutual exclusion + Hold&Wait + No preemption + Circular wait
Deadlock prevention Lock ordering / tryLock() with timeout / minimize nested locks
sleep() vs wait() sleep(): keeps lock, timed. wait(): releases lock, until notified.
notify() vs notifyAll() notifyAll() preferred — avoid missing signals with notify()
ReentrantLock tryLock, lockInterruptibly, fairness. Always unlock() in finally!
AtomicInteger Lock-free CAS-based. incrementAndGet(), compareAndSet().
Fastest.
ExecutorService Fixed/Cached/Single/Scheduled pools. submit() returns Future.
Always shutdown().
CompletableFuture Non-blocking callbacks: thenApply, thenCompose, thenAccept,
allOf.
ThreadLocal Per-thread variable. Always remove() in finally to avoid memory
leaks.
CountDownLatch await() until count reaches 0. countDown() per task. One-time
use.
Semaphore N permits. acquire()/release(). Rate limiting, throttling.
ConcurrentHashMap Thread-safe Map. No null keys/values. CAS + bucket-level locks.
🎯 FULL JAVA INTERVIEW PREP — COMPLETE!
Spring Boot ✅ Collections ✅ OOPs ✅ Exceptions ✅ Streams + Generics + FI ✅ Java Basics +
Multithreading ✅
You are ready to crack the Accenture interview. Go get it! 🚀