Back to Developer
Backend Development
Backend interview questions — Java, Spring, concurrency, and service design.
Java & OOP Fundamentals
What are the four pillars of Object-Oriented Programming in Java? Give an example of each.
1) Encapsulation: hiding internal data and exposing only what is necessary via getters/setters (e.g., a BankAccount class with a private balance field). 2) Abstraction: representing real-world concepts focusing on essentials while hiding complexity (e.g., a Payment interface with a processPayment() method). 3) Inheritance: a child class inherits attributes and methods from a parent (e.g., class Dog extends Animal). 4) Polymorphism: the same method behaves differently depending on the invoking object (e.g., animal.makeSound() barks for a dog, meows for a cat).
Explain the SOLID principles with a brief example for each.
S (Single Responsibility): each class has one reason to change — e.g., InvoicePrinter only prints, InvoiceCalculator only calculates. O (Open/Closed): classes are open for extension but closed for modification — e.g., abstract Discount with subclasses PercentageDiscount and FixedDiscount. L (Liskov Substitution): subclasses must be substitutable for their base class without breaking behavior. I (Interface Segregation): no class should implement methods it doesn't use — prefer small interfaces like Flyable, Swimmable over one large Animal interface. D (Dependency Inversion): high-level modules depend on abstractions, not concretions — e.g., UserNotifier depends on a Notifier interface instead of a concrete EmailService.
What is the difference between an interface and an abstract class in Java? When would you use each?
An interface defines a contract that classes must fulfill — it specifies what to do but not how. An abstract class can provide partial implementation along with the contract. Use interfaces to define common behavior across unrelated classes (e.g., Serializable). Use abstract classes when you want to share logic or state among closely related classes in an inheritance hierarchy.
What notable features were introduced in Java 8, Java 11, and Java 17?
Java 8 introduced functional programming features: lambdas, Streams API, Optional, and the new Date/Time API. Java 11 brought productivity improvements such as local-variable type inference with var and a modern asynchronous HTTP client API. Java 17 added records, sealed classes, and pattern matching, improving readability and type safety.
Explain the difference between == and equals() in Java.
== compares object references — it checks whether two variables point to the exact same object in memory. equals() compares the logical content or value of objects, according to how the method is overridden in the class. For example, two different String objects with the same text return false with == but true with equals().
What is the Garbage Collector in Java and what types of GC exist in the JVM?
The Garbage Collector automatically frees memory occupied by objects that are no longer referenced, preventing memory leaks. Key GC implementations: Serial GC (single-threaded, for small apps), Parallel GC (multi-threaded collection), G1 GC (region-based, default since Java 11, balances throughput and latency), and ZGC/Shenandoah (designed for ultra-low latency in large heaps).
Name some common design patterns and explain one with a Java example.
Common patterns: Singleton (ensures one instance — used for connection pools, loggers), Factory Method (delegates object creation without exposing instantiation logic), Observer (subscription model where observers react to subject changes — used in event systems like Kafka), Strategy (interchangeable algorithms at runtime), and Saga (coordinates local transactions across microservices without distributed transactions). Example — Singleton: a private constructor, a static instance field, and a public static getInstance() method guaranteeing a single instance throughout the application.
What is the Liskov Substitution Principle? Give a classic example of its violation.
The Liskov Substitution Principle (LSP) states that subclasses must be replaceable for their base classes without altering program correctness. A classic violation is making Square extend Rectangle: if Rectangle has independent setWidth/setHeight methods, a Square override that forces both dimensions equal breaks code that expects independent dimensions. The fix is to rethink the hierarchy so invariants are preserved.
What is the Interface Segregation Principle? How does it improve design?
The Interface Segregation Principle (ISP) states that no class should be forced to implement methods it does not use. Instead of one large interface with many methods, you should create smaller, focused interfaces. For example, rather than a single Animal interface with fly(), swim(), and run(), create separate Flyable, Swimmable, and Runnable interfaces. This reduces coupling and makes classes easier to implement and maintain.
What is the Strategy pattern and when would you use it?
The Strategy pattern defines a family of interchangeable algorithms encapsulated behind a common interface, allowing the behavior of a class to be selected at runtime. Use it when you have multiple ways to perform an operation and want to avoid conditionals. For example, a PaymentProcessor can accept different strategies (CreditCardStrategy, PayPalStrategy) and delegate the payment logic to the injected strategy without changing its own code.
What algorithm would you use to efficiently search for words horizontally and vertically in a character matrix?
Iterate over each row for horizontal matches and each column for vertical matches by reading characters top-to-bottom. For each starting position, check whether the remaining characters match the target word. Using a HashSet for the word list gives O(1) lookup per candidate substring, keeping overall complexity close to O(R × C × W) where W is the maximum word length.
How do you deduplicate words in the word list before processing, and why does it matter for the result?
Convert the word list to a Set before searching so that each unique word is searched exactly once. Without deduplication, a word appearing multiple times in the input could inflate its occurrence count, violating the requirement that repeated entries in the word list are counted only once.
How do you validate a character matrix on the backend before processing it?
Check that the matrix is not null or empty, that it contains at most 64 rows, and that each row contains at most 64 characters. Also verify that all rows have the same length to ensure the matrix is rectangular. Return a 400 error with a descriptive message if any constraint is violated.
What are the basic Git commands you use regularly, and what is a branch, merge, and pull request?
Common commands: git add . (stage changes), git commit -m "message" (commit), git push (send to remote), git checkout -b <branch> (create and switch branch), git pull (fetch remote changes), git merge <branch> (combine branches). A branch is an independent development line that avoids affecting main code. A merge integrates one branch's changes into another. A pull request is a formal merge request used for code review before integration, common in Git Flow and Feature Branching workflows.
What should a README file include for a take-home coding challenge submission?
It should cover: project purpose and overview, prerequisites and environment setup, steps to build and run both backend and frontend, how to run tests, and any known limitations or design decisions. Clear, minimal instructions ensure reviewers can evaluate the work without needing to contact the candidate.
What is Dependency Injection and why is it useful?
Dependency Injection (DI) is a design pattern where an object or function receives its dependencies from an external source rather than creating them itself. This decouples components from their concrete dependencies, making them easier to test (inject mocks) and easier to change (swap implementations without touching consumer code). In React, passing props or providing a Context are everyday DI examples.
Compare Object-Oriented Programming (OOP) and Functional Programming (FP). What are the key trade-offs?
OOP models a system as interacting objects that encapsulate mutable state using classes, inheritance, and polymorphism. FP models computation as a pipeline of pure functions over immutable data using higher-order functions, avoiding side effects. OOP is intuitive for domain modelling; FP excels in data-transformation pipelines, testability (pure functions are trivially unit-tested), and concurrency safety due to immutability. Modern JavaScript blends both paradigms.
What problem does the Business Delegate pattern solve, and what is its main benefit?
Presentation-layer components often interact directly with remote services, exposing service implementation details and creating tight coupling. A Business Delegate acts as a client-side proxy that encapsulates service lookup, access, and exception translation, hiding implementation details from the presentation layer and reducing the impact of service changes on client code.
What is the Session Facade pattern and why should you avoid creating one facade per use case?
Session Facade is a JEE pattern where a session bean acts as the single entry point for a group of related business-object interactions, managing their lifecycle and hiding complexity from the client. Creating one facade per use case leads to excessive granularity and complexity; it is better to group related use cases into a single Session Facade to simplify the application.
What is the Composite Entity pattern and what are the roles of a coarse-grained object and a dependent object within it?
Composite Entity is a JEE pattern that models and manages a set of related objects as a single unit to reduce coupling and improve maintainability. A coarse-grained object contains dependent objects, manages their lifecycle, and has its own lifecycle; dependent objects (often fine-grained) rely on the coarse-grained object and can form a tree structure.
What is the Transfer Object Assembler pattern and when is it useful?
Transfer Object Assembler builds a composite transfer object by aggregating data from multiple services and components, then returns it to the client in one operation. It is useful when you want to centralize business logic, create a complex presentation model without burdening the client, or reduce coupling between the client and enterprise components.
What is the Service Locator pattern in JEE and what are its main responsibilities?
Service Locator is a JEE pattern that provides a uniform, transparent mechanism for locating services and components such as JDBC data sources or asynchronous services. It centralizes and encapsulates the lookup mechanism—including initial context creation—so clients do not need to know the details of how services are found or accessed.
Spring & Spring Boot
What common Spring annotations are used to define beans, and what is dependency injection?
In Spring, @RestController marks REST endpoint classes, @Service marks business-logic components, and @Repository marks data-access components. These annotations register the classes as beans managed by the Spring IoC container. Dependency injection lets the container automatically wire beans together — typically via constructor injection or @Autowired — eliminating the need to instantiate dependencies manually with new.
How do you implement in-memory caching with a TTL in Spring Boot?
Enable caching with @EnableCaching and use @Cacheable on the method that fetches the value. For TTL control, configure a CacheManager (e.g., CaffeineCacheManager) with an expireAfterWrite duration. Example: Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build() wired into the manager. Spring will then serve cached values automatically until the entry expires.
How do you implement a cache-fallback pattern when an external service is unavailable?
Catch the exception thrown by the external call, then attempt to read the last known value from the cache. If a cached value exists, return it and log a warning. If no cached value is available, propagate an appropriate HTTP error (e.g., 503 Service Unavailable). This decouples the caller from transient downstream failures.
How do you configure automatic retry logic for an external service call in Spring Boot?
Add spring-retry to the classpath and annotate the configuration class with @EnableRetry. Then annotate the method with @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500)). After exhausting retries, a @Recover method can return a fallback value or throw a specific exception. This keeps retry concerns separate from business logic.
How do you implement rate limiting (e.g., 3 requests per minute) in a Spring Boot REST API?
A common approach is to use the Bucket4j library, which implements the token-bucket algorithm. Create a Bucket with a refill of 3 tokens per minute and apply it in a servlet filter or Spring interceptor. When the bucket is empty, return HTTP 429 Too Many Requests with a descriptive message. For multi-replica deployments, back the bucket with a distributed store like Redis.
How do you implement global HTTP error handling in Spring Boot?
Create a class annotated with @RestControllerAdvice and define @ExceptionHandler methods for specific exception types. Each handler returns a ResponseEntity with the appropriate HTTP status (4XX or 5XX) and a structured error body containing a message, timestamp, and path. This centralizes error formatting and keeps controllers clean.
How do you implement paginated queries in Spring Data JPA?
Repository methods accept a Pageable parameter and return Page<T>. The controller accepts page and size query parameters, constructs a PageRequest, and passes it to the repository. The returned Page object contains the content list, total elements, and total pages, which can be mapped to a DTO for the API response. Example: repository.findAll(PageRequest.of(page, size, Sort.by("createdAt").descending())).
What is the difference between Spring WebFlux and Spring MVC, and when would you choose WebFlux?
Spring MVC is thread-per-request and blocks on I/O; Spring WebFlux uses a non-blocking, event-loop model (Project Reactor) and handles many concurrent requests with fewer threads. WebFlux is beneficial when the service makes many downstream HTTP or database calls with high concurrency and low latency requirements. For CPU-bound or simple CRUD APIs, Spring MVC is usually simpler and sufficient.
How do you generate OpenAPI/Swagger documentation for a Spring Boot REST API?
Add the springdoc-openapi-starter-webmvc-ui dependency. Spring Boot auto-configures the /v3/api-docs endpoint and the Swagger UI at /swagger-ui.html. Enrich documentation with @Operation, @Parameter, and @ApiResponse annotations on controllers. This produces interactive, always-up-to-date documentation that clients can use to test endpoints directly from the browser.
How do you test external-service failure scenarios in a Spring Boot unit test?
Use Mockito to mock the external-service client and configure it to throw an exception with when(...).thenThrow(...). Verify that the retry mechanism fires the expected number of times and that the fallback value (cache or error response) is returned. For integration tests, WireMock can stub HTTP endpoints to return error responses, providing more realistic failure simulation.
Concurrency & the JVM
What is a thread in Java, what concurrency problems can arise, and how do you manage them?
A thread is an independent unit of execution within a process. Java's multithreading model improves efficiency but introduces risks like race conditions — when multiple threads modify shared data simultaneously, leading to unpredictable results. Solutions include synchronized blocks, explicit locks (ReentrantLock), and concurrent data structures from java.util.concurrent (e.g., ConcurrentHashMap, AtomicInteger).
What is CompletableFuture in Java and how do you chain multiple async operations?
CompletableFuture is a class that enables asynchronous, non-blocking task execution and composition. You can chain operations using methods like thenApply() (transform result), thenCompose() (flat-map another future), and thenCombine() (combine two futures). It integrates well with lambdas and allows error handling via exceptionally() or handle(), making it ideal for I/O-bound or service-call workflows.
How do you implement asynchronous call logging in Spring Boot so it does not impact the main response time?
Annotate the logging method with @Async and enable it with @EnableAsync on a configuration class. The caller fires and forgets — the main thread returns immediately while the logging executes in a separate thread pool. Wrap the async method in a try-catch so any persistence failure is silently swallowed and never propagates to the caller.
What is the Service Activator pattern and what problem does it address?
Service Activator is a JEE pattern that enables asynchronous invocation of one or more services, commonly implemented via messaging infrastructure such as JMS. It addresses the need for applications that cannot wait for a synchronous response—for example, when a business task is composed of multiple sub-tasks—by providing a model to send a request and handle the response when it becomes available.