Design patterns come up in almost every software developer interview. Interviewers use them to check how you think about code, not just how you write it. Strong answers explain the problem, the solution and the trade-offs.
This blog covers the most common design patterns interview questions. It starts with the basics for freshers. It then moves to intermediate topics, experienced-level design questions and real-world scenarios. Each answer is short, clear and ready to use in an interview.
Let’s start!
Freshers usually face basic questions on definitions, principles, and pattern categories. Interviewers want to see that you understand the “why” behind patterns. Use the answers below to build a strong foundation.

Design patterns are reusable, high-level blueprints that help developers solve recurring problems in software design. Rather than providing a direct copy-and-paste code snippet, they offer a standard template or architecture that you can adapt to fit your specific program.
It makes your code more organized, maintainable, flexible, and easier to understand. They are especially useful in object-oriented programming, where developers need to manage relationships between classes and objects.
Design patterns and algorithms solve different types of problems in software development.
A design pattern provides a reusable approach to structuring classes, objects and their interactions. An algorithm provides a sequence of steps to solve a specific computational problem.
For example, the Strategy pattern allows us to switch between different payment methods without changing the main payment processing logic. In contrast, a sorting algorithm, such as Quick Sort, arranges elements in a particular order.
The main difference is that design patterns focus on software structure and object interaction, while algorithms focus on the steps required to solve a problem. Both are important for writing efficient and maintainable code.
Design principles are guidelines that help developers write clean, flexible and maintainable code. Design patterns are reusable approaches that help developers solve common software design problems.
For example, the Open-Closed Principle states that software entities should be open for extension but closed for modification. This means developers can add new functionality without repeatedly changing existing, stable code.
The Strategy pattern helps achieve this principle by letting developers introduce new algorithms or behaviors through separate classes.
In simple terms, design principles guide how we design software, while design patterns show us how to apply suitable solutions to recurring design problems. Following good design principles also helps us choose and implement design patterns effectively.
The Java Development Kit (JDK) uses several design patterns to make its APIs flexible, reusable and easier to work with. Some common examples are:
| Design pattern | Java example | Purpose |
| Singleton | Runtime.getRuntime() | Provides access to a shared runtime instance. |
| Factory Method | Calendar.getInstance() | Creates and returns an appropriate calendar instance. |
| Observer | PropertyChangeListener with PropertyChangeSupport | Notifies registered listeners when a property changes. |
| Iterator | Iterator interface | Allows sequential access to elements in a collection. |
| Decorator | BufferedInputStream | Adds buffering functionality to an existing input stream. |
| Adapter | Arrays.asList() | Adapts an array to a list-style interface, with specific limitations. |
These examples show how design patterns appear in everyday Java programming. However, not every API maps perfectly to one pattern in every implementation. Understanding the purpose of each example is more important than simply memorizing class names.
The Open-Closed Principle, or OCP, is one of the five SOLID principles of object-oriented design. It states that software entities, such as classes and modules, should be open for extension but closed for modification.
This means developers should be able to add new functionality without making unnecessary changes to existing, stable code.
For example, imagine an application that supports credit card payments. Later, the business needs to support UPI payments as well.
Instead of adding more conditional statements to a single payment-processing class every time a new payment method is introduced, we can define a PaymentMethod interface and create separate implementations for credit cards and UPI.
When we introduce another payment method, we can add a new implementation without changing the core payment-processing logic.
This approach reduces the risk of breaking existing functionality and makes the application easier to maintain and extend. Design patterns such as Strategy can help implement the Open-Closed Principle.
SOLID is an acronym for five object-oriented design principles. These principles help developers create software that is easier to maintain, test, understand and extend.

The five SOLID principles are:
1. Single Responsibility Principle (SRP): A class should have one responsibility and one main reason to change. For example, an invoice class should not also handle email delivery.
2. Open-Closed Principle (OCP): Software entities should allow new functionality without requiring unnecessary changes to existing code. For example, we can add a new payment method by implementing an existing interface.
3. Liskov Substitution Principle (LSP): Objects of a subclass should be usable wherever objects of the parent type are expected without breaking the intended behavior. This helps ensure that inheritance works correctly.
4. Interface Segregation Principle (ISP): Clients should not be forced to depend on methods they do not use. For example, several smaller, focused interfaces may be better than one large interface that contains unrelated methods.
5. Dependency Inversion Principle (DIP): High-level modules should depend on abstractions rather than concrete implementations. For example, a notification service can depend on a messaging interface instead of being tightly coupled to one email provider.
Together, these principles encourage loosely coupled, modular and maintainable software. They also help developers decide when a design pattern can improve an application's structure.
The Gang of Four, commonly known as GoF, refers to four software engineering authors: Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.
They wrote the influential book Design Patterns: Elements of Reusable Object-Oriented Software, published in 1994. The book documented 23 classic design patterns that address recurring problems in object-oriented software design.
These patterns are divided into three main categories:
The GoF patterns remain important because they provide a common vocabulary for discussing software architecture and reusable design solutions. Developers can use them to communicate design decisions more clearly and build maintainable applications.
Inversion of Control, or IoC, is a software design principle in which control over certain tasks, such as creating objects or managing dependencies, is transferred from application code to an external framework or container.
Without IoC, a class may create its own dependencies directly. With IoC, an external component can provide those dependencies instead.
For example, imagine a notification service that needs an email sender. Without IoC, the service might create an EmailSender object inside its constructor. This makes the service tightly coupled to that specific implementation.
With IoC, the application can provide an implementation through a constructor parameter or an interface. The notification service no longer needs to create the dependency itself.
Frameworks such as Spring use IoC containers to manage objects and their dependencies. Dependency Injection is a common way to implement IoC.
The main benefit of Inversion of Control is that it reduces tight coupling and makes software easier to test, maintain and extend.
Design patterns in Java are commonly divided into three categories: creational, structural and behavioral. Each category addresses a different aspect of software design.
Creational patterns focus on how objects are created. They help developers manage object creation without unnecessarily coupling the application to specific classes.
Examples include:
Structural patterns focus on how classes and objects are combined to form larger structures.
Examples include:
Behavioral patterns focus on communication, responsibilities and interactions between objects.
Examples include:
Understanding these three categories helps Java developers select an appropriate design pattern based on the problem they need to solve, rather than applying patterns unnecessarily.
I would describe a design pattern by explaining the problem it solves, the context in which it is useful, its structure and the benefits and trade-offs of using it.
A well-documented design pattern generally includes the following elements:
For example, the Strategy pattern addresses situations where an application needs to support multiple interchangeable algorithms or behaviors.
The solution is to define a common interface and implement each behavior separately. The application can then select the appropriate implementation at runtime.
This design makes it easier to introduce new behaviors without placing all the logic inside one large class. However, it may introduce additional classes and complexity, so I would use it when the flexibility provides a real benefit.
In my view, a good understanding of design patterns means knowing not only their definitions but also when to use them, how to implement them and when a simpler solution would be more appropriate.
Intermediate questions go deeper into specific patterns. Interviewers expect you to give examples, name trade-offs and show when you would use each pattern. This section covers the patterns that appear most often.
The main advantage of the Prototype Design Pattern is that it allows us to create new objects by copying existing objects instead of creating them from scratch using the new keyword. This is useful when object creation is expensive or when we need multiple objects with similar configurations.
For example, suppose an application creates complex report objects with predefined settings, templates and formatting rules. Instead of initializing every report from scratch, we can create a prototype report and clone it whenever we need a similar report.
The Prototype Pattern reduces repeated initialization and makes object creation more flexible. However, we need to handle shallow and deep copying carefully, especially when objects contain references to mutable data.
The Decorator Design Pattern is a structural design pattern that allows us to add new functionality to an existing object without modifying its original class. It works by wrapping an object inside another object that follows the same interface.
For example, consider a coffee-ordering application. We can start with a basic coffee and add decorators for milk, sugar, or extra espresso. Each decorator adds functionality or cost without requiring separate classes for every possible combination.
The main advantage of the Decorator Pattern is that it follows the Open-Closed Principle. We can extend an object's behavior without modifying existing code. It is particularly useful when we need flexible combinations of features at runtime.

The Command Pattern is a behavioral design pattern that encapsulates a request as an object. It separates the object that requests an action from the object that performs it.
For example, in a text editor, actions such as copy, paste, undo and redo can be represented as command objects. Each command contains the logic required to execute a specific action.
This pattern is useful when we need to queue requests, maintain action history, support undo and redo operations, or schedule tasks for later execution. It also makes the application easier to extend because we can introduce new commands without changing the code that invokes them.

The Observer Design Pattern is a behavioral design pattern that establishes a one-to-many relationship between objects. When one object changes its state, all registered observers receive a notification about that change.
For example, consider a stock market application. When the price of a stock changes, the application can notify all users who have subscribed to updates for that stock. The stock object does not need to know how each user displays the notification.
The Observer Pattern reduces tight coupling between the object that produces an update and the objects that receive it. It is commonly used in event-driven applications, notification systems and user interface frameworks. However, we should manage subscriptions carefully to avoid memory leaks and unnecessary notifications.
The Builder Design Pattern solves the problem of creating complex objects that require multiple configuration options or construction steps. It avoids constructors with too many parameters and makes object creation easier to understand.
For example, suppose we are developing an application that creates user accounts with optional settings such as an email address, phone number, profile picture and notification preferences. A constructor with many parameters can become difficult to read and maintain.
With the Builder Pattern, we can set only the required properties and then call a final method to create the object. This makes object construction more readable and reduces errors caused by confusing parameter order.
The Builder Pattern is particularly useful for complex configuration objects, API request objects and immutable objects. It is not always necessary for simple objects with only a few fields.
The Null Object Pattern is a behavioral design pattern that provides an object with default behavior instead of returning null. This allows the application to call methods on that object without repeatedly checking whether it exists.
For example, consider a notification system in which some users have not configured a notification service. Instead of returning null, the application can return a NoNotification object that implements the same interface but performs no action.
This reduces repetitive null checks and makes the code easier to maintain. It can also help prevent NullPointerException errors in Java.
However, the Null Object Pattern is appropriate only when doing nothing is a valid behavior. We should not use it to hide unexpected errors or situations in which a missing object requires special handling.
The MVC Design Pattern stands for Model-View-Controller. It is an architectural pattern that separates an application's data, user interface and request-handling logic into three components.
The three components are:
For example, in an online shopping application, the Model manages product and order data, the View displays product details and the Controller handles actions such as adding a product to the cart.
The main advantage of MVC is separation of concerns. Each component has a specific responsibility, which makes the application easier to test, maintain and modify. MVC is commonly used in web applications and frameworks such as Spring MVC and ASP.NET MVC.
The Composite Entity Pattern is a J2EE design pattern that manages a group of related objects as a single entity. It helps manage complex data relationships and reduces the need for frequent communication between client code and individual objects.
Its main components are:
For example, in an employee management system, an employee record may contain personal details, contact information and employment details. The application can manage these related objects through a single composite entity.
The main benefit is that it simplifies access to related data and can reduce remote calls in distributed applications. However, this pattern is primarily associated with traditional J2EE enterprise applications, so we should distinguish it from the Composite Pattern, which organizes objects into tree-like structures.
The Chain of Responsibility Pattern is a behavioral design pattern that passes a request through a sequence of handlers. Each handler decides whether to process the request or pass it to the next handler in the chain.
For example, consider an online payment system. A payment request may pass through several handlers that validate the payment details, check fraud risks, verify transaction limits and authorize the payment. Each handler performs its own validation before forwarding the request.
We should use this pattern when multiple objects can handle a request, when the processing order matters, or when we want to change the processing sequence without modifying the client code.
It is also useful in logging systems, authentication pipelines, request validation and middleware.
The main advantage is loose coupling between the sender and the handlers. However, we must design the chain carefully because a request might remain unprocessed if no handler accepts it.
A thread-safe Singleton Pattern ensures that an application creates only one instance of a class, even when multiple threads try to access it simultaneously.
In Java, we can achieve thread safety in several ways. The appropriate approach depends on whether we need lazy initialization, simplicity, or protection against serialization and reflection-related issues.
| public class Main { enum DatabaseConnection { INSTANCE } public static void main(String[] args) { DatabaseConnection db1 = DatabaseConnection.INSTANCE; DatabaseConnection db2 = DatabaseConnection.INSTANCE; System.out.println(db1 == db2); } } |

Java initializes enum constants safely and provides built-in protection against creating additional enum instances through normal serialization and reflection mechanisms. This is one of the simplest approaches for implementing a Singleton.
| public class Main { private Main() {} private static class Holder { private static final Main INSTANCE = new Main(); } public static Main getInstance() { return Holder.INSTANCE; } public static void main(String[] args) { Main db1 = Main.getInstance(); Main db2 = Main.getInstance(); System.out.println(db1 == db2); } } |

This approach uses Java's class initialization guarantees to create the instance safely when it is first requested. It supports lazy initialization without requiring explicit synchronization.
| public class Main { private static volatile Main instance; private Main() {} public static Main getInstance() { if (instance == null) { synchronized (Main.class) { if (instance == null) { instance = new Main(); } } } return instance; } public static void main(String[] args) { Main db1 = Main.getInstance(); Main db2 = Main.getInstance(); System.out.println(db1 == db2); } } |

The volatile keyword ensures that threads see the properly initialized instance. The synchronized block prevents multiple threads from creating separate instances during initialization, while the outer check avoids synchronization on subsequent calls.
For most applications, I would prefer the enum approach for a simple Singleton or the static inner class approach when I need lazy initialization. I would use double-checked locking only when there is a specific reason to choose it.
The important point is that thread safety requires more than a private constructor. We must also ensure that concurrent access cannot create multiple instances.
Experienced candidates face design and architecture questions. Interviewers want to hear how you combine patterns, handle failures and avoid overengineering. Answer with a clear approach, a real example and a trade-off.
I would start by understanding the existing monolith before making any changes. I would identify tightly coupled components, duplicated logic, performance bottlenecks and modules that frequently cause bugs. I would also review the existing tests and establish a baseline for application performance.
Next, I would refactor the application incrementally rather than rewriting everything at once. I would use the Facade pattern to simplify interactions with complex subsystems, the Strategy pattern to separate interchangeable business rules and the Dependency Injection pattern to reduce tight coupling between components.
For example, if an e-commerce application has payment logic mixed with order processing, I would extract the payment logic behind a common interface. The order service could then work with different payment providers without depending on their internal implementations.
I would introduce automated tests before changing critical modules. I would also use feature flags, backward-compatible APIs and gradual rollouts to reduce deployment risks.
My goal would be to improve the application's internal structure without changing its existing behavior. I would consider microservices only when independent deployment, scaling requirements, or team ownership justify the additional operational complexity.
Key takeaway: I would refactor incrementally, validate every change and introduce design patterns only where they solve a real architectural problem
I would design the notification system around a common NotificationSender interface. Email, SMS and push notification providers would implement this interface and handle their own delivery logic.
I would use the Strategy pattern to encapsulate each notification method. This would allow the application to switch between notification channels without changing the business logic.
The Factory pattern would select the appropriate notification strategy based on the requested channel. Dependency Injection would provide the required implementations and their dependencies, such as provider clients and configuration settings.
For example, when a user requests a password reset, the notification service could select email as the delivery channel. If the business requirements allow SMS as an alternative, the service could select the SMS implementation instead.
I would also consider provider failures, rate limits, retries and delivery tracking. For production workloads, I would use a message queue to process notifications asynchronously. I would add idempotency controls to prevent duplicate deliveries when jobs are retried.
I would keep the notification channel separate from the delivery provider when necessary. For example, an email notification could use different providers without changing the business rules.
Key takeaway: Strategy handles interchangeable behavior, Factory handles implementation selection and Dependency Injection manages dependencies.
I would choose between the Observer and Pub/Sub patterns based on the application's communication requirements.
The Observer pattern works well when multiple components need to react to changes in a subject. Pub/Sub is more suitable for distributed systems where publishers and subscribers communicate through a message broker without depending directly on each other.
For example, when an order is placed in an e-commerce application, the order service could publish an OrderPlaced event. The inventory service could update stock, the notification service could send a confirmation and the analytics service could record the transaction independently.
However, I would not assume that every event is delivered exactly once. Distributed systems can produce duplicate messages, temporary failures and out-of-order events.
To handle these issues, I would implement the following safeguards:
I would also make consumers tolerant of duplicate delivery and define clear rules for event ordering where business operations require it.
Key takeaway: An event-driven architecture needs reliable publishing, idempotent consumers, controlled retries and monitoring. Choosing Observer or Pub/Sub alone does not guarantee reliability.
I would choose between the Decorator, Proxy and Adapter patterns based on the problem I need to solve. Although all three patterns wrap an existing object, they serve different purposes.
I use it when I need to add new behavior to an object without changing its original implementation. For example, I can add caching, logging, or performance monitoring to a service.
I use it when I need to control access to an object. For example, a proxy can implement authorization checks, lazy loading, rate limiting, or remote service communication.
I use it when two components have incompatible interfaces. For example, if my application uses a standard payment interface but an external payment provider exposes a different API, I can create an adapter to make both interfaces compatible.
Let me explain this with a practical example. Suppose I have a payment service that processes transactions.
If I want to add logging without modifying the payment service, I would use the Decorator pattern. If I want to restrict access to the payment service or delay its initialization, I would use the Proxy pattern. If I want to integrate a third-party payment gateway with a different interface, I would use the Adapter pattern.
I would also consider the Single Responsibility Principle. Each pattern should solve one clear problem instead of introducing unnecessary abstraction.
My rule of thumb is simple: Decorator adds behavior, Proxy controls access and Adapter makes incompatible interfaces work together.
I would design a thread-safe, lazily initialized Singleton by ensuring that the application creates only one instance when it is first needed. I would also make sure that multiple threads cannot create separate instances during simultaneous access.
In Java, my preferred approach would be a static inner helper class, also known as the Initialization-on-Demand Holder Idiom.
| public class Main { public static void main(String[] args) { AppConfig config1 = AppConfig.getInstance(); AppConfig config2 = AppConfig.getInstance(); System.out.println(config1 == config2); } public static final class AppConfig { private AppConfig() { // Prevent external instantiation } private static class Holder { private static final AppConfig INSTANCE = new AppConfig(); } public static AppConfig getInstance() { return Holder.INSTANCE; } } } |

This approach is thread-safe because Java initializes the inner class when it is first accessed. The JVM guarantees safe class initialization, so I do not need explicit synchronization or double-checked locking.
I would consider a few important risks.
At last I will question whether a Singleton is necessary at all. For application services, dependency injection is often easier to test and maintain. A Singleton is most appropriate when a single shared instance is a genuine requirement, not just a convenient shortcut.
I would create a Handler interface with one method, such as handle(request, next). Each concern would become its own handler. The pipeline would run in this order:
Error handling wraps the whole chain. I would place it at the outermost layer so it can catch failures from any handler and return a consistent response.
Order matters. For example, I would authenticate before I validate. This stops unknown callers from reaching deeper logic. Each handler would stop the chain and return an error if its check fails.
I would also keep handlers small and independent. Teams can then add, remove, or reorder handlers through configuration without changing the client code. Web frameworks such as servlet filters and middleware stacks use this idea.
Key takeaway: Give each concern one handler, set the order with care and wrap the chain with central error handling.
I would use State for the payment lifecycle and Strategy for the payment providers.
For retries, I would retry only temporary errors, such as timeouts. I would use exponential backoff and a unique idempotency key for every payment. This prevents double charges. For permanent errors, such as a declined card, I would move the payment to Failed and stop.
I would store every state change for auditing. I would also add a fallback provider for outages, plus monitoring and alerts.
Key takeaway: State controls what a payment can do next. Strategy controls who processes it. Idempotency keys keep retries safe.
A large factory with a long switch statement is hard to maintain. It also breaks the Open-Closed Principle, because every new plugin needs an edit.
I would use these approaches:
I would also add tests that check every registered plugin follows the contract. Clear documentation and version rules help teams add plugins safely.
Key takeaway: Replace the switch statement with a registry, so adding a plugin never requires editing the factory.
The Command pattern turns each action into an object. That object can run, store, undo and replay.
Key takeaway: Because commands are objects, you can store them, replay them and reverse them.
I would look for warning signs first:
Next, I would measure. I would profile the code and review how often the code changes. If a pattern adds cost but does not support real change, I would remove it.
I would refactor in small steps. I would add tests, inline the unneeded abstraction and keep the behavior the same. For example, I would replace a single-implementation Strategy with a direct method call.
I follow a simple rule: use a pattern when it solves a problem I have today or a change I can reasonably expect. Simple code is often the better design.
Key takeaway: Patterns are tools. Remove them when they cost more than they give.
Scenario-based questions test how you solve real problems. Interviewers want to see how you combine patterns, plan for failure and explain your trade-offs. Structure each answer as problem, patterns, flow and risks.
I would combine several patterns.
For human approval, I would pause the workflow in the Waiting for Approval state and save its data. The system would resume after a person approves or rejects the step. I would also use idempotency keys, so a retry does not repeat an action such as sending an email.
Key takeaway: Use Command and State for control, Retry and Circuit Breaker for failures and Saga for recovery.
I would also add a transactional outbox, retries with backoff and dead-letter queues. Monitoring would alert the team when messages fail repeatedly.
Key takeaway: Pub/Sub decouples services, Idempotency makes duplicates safe and Saga handles partial failures.
Each pattern solves one part of the problem.
I would also add a fallback, such as cached data or a default response. Metrics and alerts would show which dependency is failing.
Key takeaway: Timeouts limit waiting, Retries handle brief errors, Circuit Breakers stop repeated failures and Bulkheads contain the damage.
For data consistency, I would avoid a big-bang data move. I would sync data between old and new systems with events or change data capture. For a short period, one system would own each piece of data. For multi-service workflows, I would use Sagas instead of distributed transactions.
I would use feature flags and gradual rollouts, so I can roll back quickly if something breaks.
Key takeaway: Migrate one feature at a time, protect new code with an anti-corruption layer and give each piece of data a single owner.
I would design the gateway in layers.
For example, a request arrives and passes through authentication and policy checks. The strategy picks a model. The factory builds its client. The proxy calls the model, logs the action and falls back to another model if the first one fails.
Key takeaway: Chain of Responsibility enforces rules, Strategy selects the model, Factory builds the client and Proxy secures and monitors each call.
Design patterns interviews reward clear thinking more than memorized definitions. For every pattern, learn the problem it solves, a simple real-world example and one trade-off. Practice explaining each answer out loud in under two minutes.
Start with the basics and the GoF categories. Then practice the Singleton, Factory, Strategy, Observer and Decorator patterns in code. Finally, work on the scenario questions, because they show how you apply patterns in real systems. With steady practice, you can answer any design patterns interview question with confidence.