Home/Blog/Interview Prep/Top design patterns interview questions
Home/Blog/Interview Prep/Top design patterns interview questions

Top design patterns interview questions

14 min read
Dec 04, 2024
content
1. What are design patterns?
2. What are the SOLID principles?
3. What is Inversion of Control?
4. What is Dependency Injection?
5. What are the types of design patterns?
6. What is the Singleton Pattern, and when is it appropriate to use?
7. How to create a singleton class?
8. What is the Factory Pattern, and what are its common use cases?
9. How does the Abstract Factory Pattern differ from the Factory Pattern?
10. What is the Strategy Pattern, and where is it commonly used?
11. What is the Observer Pattern, and when would you use it?
12. What are the main components of the Observer Pattern?
13. What is the Decorator Pattern, and what are its common use cases?
Conclusion

Design patterns are a fundamental aspect of software development that offer proven, reusable solutions to common design problems. These patterns, refined over time by experienced developers, serve as blueprints that help teams create maintainable, flexible code that adheres to best practices. In larger projects, design patterns play a crucial role in maintaining consistency, avoiding duplication, enhancing modularity, and ensuring scalability.

If you’re preparing for a technical interview, particularly for roles involving software design or architecture, a strong understanding of design patterns can set you apart. Interviewers often look beyond coding skills to assess your ability to design systems that are efficient and scalable. Demonstrating a deep knowledge of design patterns shows that you can think critically and apply structured, effective solutions to complex problems.

In this blog, we’ll explore some of the most common design pattern interview questions that can help you excel in your design interviews. We’ll cover a range of questions, diving into key details such as theoretical concepts, class diagrams, and code implementations of popular design patterns, including Singleton Pattern, Factory Pattern, Strategy Pattern, Observer Pattern, and Decorator Pattern.

1. What are design patterns?#

Design patterns are general, reusable solutions to common problems in software design. They serve as templates for writing code that can meet the requirements of a particular design problem.

Pause and ponder: How do design patterns differ from algorithms?

Cover
Software Design Patterns: Best Practices for Software Developers

Being good at problem-solving is one thing but to take your career to the next level, one must know how complex software projects are architected. Software design patterns provide templates and tricks used to design and solve recurring software problems and tasks. Applying time-tested patterns result in extensible, maintainable and flexible high-quality code, exhibiting superior craftsmanship of a software engineer. Being well-versed in knowledge of design patterns allows one to spot brittle and immature code from miles away. The course goes to great lengths to lay bare the esoteric concepts of various design patterns before the reader and is replete with real-world examples and sample code. The readership for this course is expected to be familiar with some object orientated language. The course examples and snippets are written in the Java language but folks with experience in other OOP languages should easily be able to follow the discussion and code intent.

8hrs
Beginner
93 Exercises
31 Illustrations

2. What are the SOLID principles?#

The SOLID principles are a set of five key guidelines for writing clean and maintainable code in software development:

  1. Single responsibility principle (SRP): A class should have only one job or responsibility.

  2. Open/closed principle (OCP): Classes should be open for extension but closed for modification.

  3. Liskov substitution principle (LSP): Subclasses should be substitutable for their base classes.

  4. Interface segregation principle (ISP): Use specific interfaces instead of a broad one to prevent dependencies on unused methods.

  5. Dependency inversion principle (DIP): Depend on abstractions, not concrete implementations.

SOLID principles
SOLID principles

3. What is Inversion of Control?#

Inversion of Control (IoC) is a design principle that transfers the control of object creation and dependency management from the application code to an external framework or container. This approach helps achieve loose coupling, making the code more modular, flexible, and easier to test by having the framework handle dependencies instead of the code itself. Common techniques for IoC include Dependency Injection and Service Locator.

4. What is Dependency Injection?#

Dependency Injection (DI) is a design pattern and technique in which dependencies (e.g., objects or services) are injected into a class rather than being created within the class itself. This allows the class to delegate the responsibility of creating dependencies to an external entity, such as a framework, container, or the calling code.

There are several types of Dependency Injection:

  • Constructor Injection: Dependencies are provided through the class constructor.

  • Setter Injection: Dependencies are set through public setter methods after the object is created.

  • Interface Injection: Dependencies are injected through an interface that the class implements.

Let’s consider a scenario where we have two classes, A and B, with class A depending on class B. Without IoC, class A creates an object of B internally, tightly coupling the two classes. However, with IoC in action, the object of class B is created outside of class A and then injected into it via its constructor, allowing it to use B without directly managing its creation. The following C++ code demonstrates both scenarios.

// Without IoC: Class A directly depends on Class B
class B {
public:
void doSomething() {
std::cout << "Doing something in B." << endl;
}
};
class A {
B* b;
public:
// Direct dependency on B using the new operator
A() {
b = new B();
}
void useB() {
b->doSomething();
}
~A() {
delete b; // Clean up
}
};
int main() {
A a;
a.useB();
return 0;
}
Example of Dependency Injection

5. What are the types of design patterns?#

Design patterns can be categorized into three main types:

  1. Creational design patterns: These patterns are used to provide a mechanism for creating objects in a specific situation without revealing the creation method. These patterns allow flexibility in deciding which objects need to be created for a specific use case by providing control over the creation process.

  2. Structural design patterns: These are concerned with class/object composition and relationships between objects. They let us add new functionalities to objects so that restructuring some parts of the system does not affect the rest.

  3. Behavioral design patterns: These are concerned with communication between dissimilar objects in a system. They streamline communication and ensure that the information is synchronized between such objects.

The illustration below highlights some of the most commonly used design patterns from each of the three types mentioned above.

The primary types of design patterns can be summarized as:

  • Creational design patterns: Deal with object creation mechanisms.

  • Structural design patterns: Concerned with the composition of classes or objects.

  • Behavioral design patterns: Focus on communication between objects.

6. What is the Singleton Pattern, and when is it appropriate to use?#

The Singleton is a creational design pattern that ensures that a class has only one instance and provides a global point of access to that instance.

It is used when a single, shared resource is needed throughout an application, such as a configuration manager, logging service, or database connection, to avoid the overhead of multiple object creations and ensure consistent access to shared data.

Singleton Pattern
Singleton Pattern

7. How to create a singleton class?#

A singleton class is designed to ensure that only one instance of the class exists throughout the application. To create a singleton class, we need to make the constructor private, provide a static method that returns the instance, and store the instance in a private static variable.

class Singleton {
private:
// Private static instance variable
static Singleton* instance;
// Private constructor to prevent instantiation
Singleton() {}
public:
// Static method to access the single instance
static Singleton* getInstance() {
if (instance == nullptr)
instance = new Singleton();
return instance;
}
void doSomething() {
std::cout << "Singleton instance in C++." << endl;
}
};
// Initialize the static instance variable
Singleton* Singleton::instance = nullptr;
// Driver code
int main() {
Singleton* singleton = Singleton::getInstance();
singleton->doSomething();
return 0;
}

Pause and ponder: What is the difference between a static class and a singleton class?

8. What is the Factory Pattern, and what are its common use cases?#

The Factory Pattern is a creational design pattern that provides a way to create objects without specifying the exact class of object that will be created. It defines a method, called a factory method, that is responsible for instantiating the objects, allowing subclasses or factory classes to decide which class to instantiate. This promotes loose coupling and enhances flexibility by centralizing object creation.

Factory Pattern
Factory Pattern

Imagine we have two types of cars, Hatchback and Sedan, both of which extend from the parent class Car. To create these different car types, we introduce a CarFactory class. This factory class provides the method createCar(), which returns a new Car object based on the specified type ("H" for Hatchback, "S" for Sedan). This approach enables the main program to create and use different car types without needing to know the exact class details.

class Car {
public:
virtual void drive() = 0;
virtual ~Car() {}
};
class Hatchback : public Car {
public:
void drive() override {
std::cout << "Driving Hatchback" << std::endl;
}
};
class Sedan : public Car {
public:
void drive() override {
std::cout << "Driving Sedan" << std::endl;
}
};
class CarFactory {
public:
Car* createCar(const std::string& type) {
if (type == "H") {
return new Hatchback();
} else if (type == "S") {
return new Sedan();
} else {
return nullptr;
}
}
};
int main() {
CarFactory carFactory;
Car* hatchback = carFactory.createCar("H");
Car* sedan = carFactory.createCar("S");
if (hatchback) {
hatchback->drive();
delete hatchback;
}
if (sedan) {
sedan->drive();
delete sedan;
}
return 0;
}

Common use cases of the Factory Pattern:

  • Object creation without specifying the exact class: We need to create objects without specifying the exact class of object being created.

    • Example: Generating various types of notifications (e.g., SMS, Email, Push) in an alert system based on user preferences.

  • Centralized creation logic: The creation logic needs to be centralized, making it easier to manage and modify.

    • Example: Database connection creation (e.g., MySQL, SQLite) to easily switch between different database types without altering the main codebase.

  • Flexible object creation: We want to provide flexibility in the type of objects created, such as when objects are selected based on input parameters or configuration.

    • Example: Creating different payment gateways (e.g., PayPal, Stripe) in an e-commerce platform based on user selection.

  • Encapsulating instantiation logic: We need to encapsulate the instantiation logic and reduce code duplication, especially when dealing with complex object creation processes.

    • Example: Instantiating complex GUI components (e.g., buttons, sliders) in a software application to avoid redundant setup code.

9. How does the Abstract Factory Pattern differ from the Factory Pattern?#

The Abstract Factory Pattern and Factory Pattern are both creational design patterns used for object creation, but they differ in their scope and intent. The table below highlights the key difference between the two patterns.

Factory Pattern

Abstract Factory Pattern

The Factory Pattern creates individual objects based on a single product type, focusing on object creation without specifying exact classes.

The Abstract Factory Pattern provides an interface to create families of related objects, ensuring consistency among products

Example: A car factory that builds different types of cars (e.g., Sedan, SUV) using a single method to create specific models.

Example: An automotive company that produces entire sets of parts (engine, tires, interiors) for different car models, ensuring that the parts fit together consistently for each car type.

While the Factory Pattern is used for a single product line, the Abstract Factory Pattern handles multiple related products, offering a higher level of abstraction in object creation.

10. What is the Strategy Pattern, and where is it commonly used?#

The Strategy Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one as a separate strategy, and make them interchangeable. This pattern enables the behavior of a class to be changed at runtime by selecting from different strategies, promoting flexibility, reusability, and clean code separation.

Strategy Pattern
Strategy Pattern

Imagine we have different sorting techniques available to sort the data. We create a strategy class, SortStrategy, that defines a sort() method, which must be implemented by all concrete sorting strategies. These include BubbleSort and STLSort, each providing its specific sorting algorithm. The SortContext class holds a reference to a SortStrategy and uses it to perform the sorting. The strategy can be set or changed at runtime using the setStrategy method, allowing the SortContext to easily switch between different sorting behaviors without altering its own implementation.

class SortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
};
class BubbleSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
int n = data.size();
for (int i = 0; i < n - 1; ++i)
for (int j = 0; j < n - i - 1; ++j)
if (data[j] > data[j + 1])
std::swap(data[j], data[j + 1]);
std::cout << "Sorted using Bubble Sort.\n";
}
};
class STLSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
std::sort(data.begin(), data.end());
std::cout << "Sorted using STL Sort.\n";
}
};
class SortContext {
private:
SortStrategy* strategy;
public:
SortContext(SortStrategy* strategy) : strategy(strategy) {}
void setStrategy(SortStrategy* newStrategy) {
strategy = newStrategy;
}
void sortData(std::vector<int>& data) {
strategy->sort(data);
}
};
// Driver code
int main() {
std::vector<int> data = {5, 3, 8, 6, 1, 9};
// Using Bubble Sort
SortContext context(new BubbleSort());
context.sortData(data);
// Changing strategy to STL Sort
context.setStrategy(new STLSort());
context.sortData(data);
return 0;
}

Common use cases of the Strategy Pattern:

  • Dynamic algorithm selection: Useful when you need to choose between different algorithms, such as sorting methods, at runtime.

    • Example: Switching between different sorting algorithms (e.g., quicksort, mergesort) based on data size or type.

  • Payment methods: Used in e-commerce applications to handle different payment methods like credit cards, PayPal, or bank transfers.

    • Example: Dynamically selecting the appropriate payment method based on user choice at checkout.

  • Compression algorithms: To choose between different compression strategies (e.g., ZIP, RAR) dynamically.

    • Example: Allowing users to choose between different file compression options when saving data.

  • Data parsing: To handle different data formats (e.g., JSON, XML, CSV) with interchangeable parsing strategies.

    • Example: Switching between JSON and XML parsers based on the data format received from an API.

  • Game development: For implementing different character behaviors (e.g., aggressive, defensive) in a flexible way.

    • Example: Changing AI behavior dynamically during gameplay based on the player’s actions.

  • UI themes: To change application themes or layouts without altering the core logic.

    • Example: Allowing users to switch between light and dark modes without changing the underlying UI components.

11. What is the Observer Pattern, and when would you use it?#

The Observer Pattern is a behavioral design pattern that establishes a one-to-many relationship between objects, allowing one object (the subject) to notify multiple other objects (observers) of any state changes automatically. This pattern is commonly used when changes in one object need to be reflected in other dependent objects, such as in event handling systems, GUI applications, or real-time notifications.

Observer Pattern
Observer Pattern

The code below demonstrates a simple implementation of the Observer Pattern, as illustrated in the diagram above. The Subject class maintains a list of observers that need to be notified when its state changes. The Observer class is an abstract base class with an update() method that concrete observers (FirstObserver and SecondObserver) must implement to respond to notifications. The Subject class allows adding and removing observers and uses the notifyObservers() method to inform all registered observers of changes by calling their update() methods.

class Observer {
public:
virtual void update(const std::string &message) = 0;
};
class FirstObserver : public Observer {
public:
void update(const std::string &message) override {
std::cout << "First observer received message: " << message << std::endl;
}
};
class SecondObserver : public Observer {
public:
void update(const std::string &message) override {
std::cout << "Second observer received message: " << message << std::endl;
}
};
class Subject {
std::vector<Observer *> observers;
public:
void addObserver(Observer *observer) {
observers.push_back(observer);
}
void removeObserver(Observer *observer) {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notifyObservers(const std::string &message) {
for (auto observer : observers) {
observer->update(message);
}
}
};
int main() {
Subject subject;
FirstObserver firstObserver;
SecondObserver secondObserver;
subject.addObserver(&firstObserver);
subject.addObserver(&secondObserver);
subject.notifyObservers("State changed!");
subject.removeObserver(&firstObserver);
subject.notifyObservers("Another state change!");
return 0;
}

Common use cases of the Observer Pattern:

  • State change notification: When an object’s state change needs to be communicated to multiple other objects.

    • Example: Updating multiple views when the data model changes in an MVC (Model-View-Controller) architecture.

  • Event-driven systems: In event-driven systems where components need to react to changes in other parts of the system.

    • Example: GUI applications where button clicks or other events trigger updates in different parts of the program.

  • Distributed event handling and real-time updates: To implement distributed event handling and real-time updates.

    • Example: Real-time data feeds in financial applications where multiple observers need to be notified of stock price changes.

12. What are the main components of the Observer Pattern?#

The Observer Pattern consists of three main components:

  1. Subject (Observable): The Subject is the core component that maintains a list of observers (subscribers) and notifies them of any state changes. It provides methods to add, remove, and notify observers.

  2. Observer: The Observer is an interface or abstract class that defines the update method. The subject calls this method when its state changes. Each observer implements this method to perform specific actions when notified.

  3. Concrete Observer: A Concrete Observer is an implementation of the Observer interface. It contains specific code to execute when the subject notifies it of a change, allowing it to react appropriately based on the state of the subject.

These components work together to create a one-to-many dependency where the Subject automatically informs its Observers about state changes, promoting loose coupling between the objects involved.

13. What is the Decorator Pattern, and what are its common use cases?#

The Decorator Pattern is a structural design pattern that allows us to add new functionality to objects dynamically without modifying their original code. It achieves this by wrapping the object in a series of decorator classes, each adding its own behavior. This pattern provides a flexible alternative to subclassing for extending functionalities, making the code more modular and easier to maintain.

Decorator Pattern
Decorator Pattern

The following code provides a basic implementation of the Decorator Pattern as depicted in the diagram above. The Coffee class is an abstract base class with a describe() method, which is implemented by SimpleCoffee to describe basic coffee. CoffeeDecorator wraps a Coffee object and delegates the describe() call to it. Concrete decorators, such as MilkDecorator and SugarDecorator, extend CoffeeDecorator and enhance the description by adding specific features like “Milk” or “Sugar” while preserving existing behavior.

class Coffee {
public:
virtual void describe() const = 0;
};
class SimpleCoffee : public Coffee {
public:
void describe() const override {
std::cout << "Simple Coffee";
}
};
class CoffeeDecorator : public Coffee {
protected:
Coffee* coffee;
public:
CoffeeDecorator(Coffee* c) : coffee(c) {}
void describe() const override {
coffee->describe();
}
};
class MilkDecorator : public CoffeeDecorator {
public:
MilkDecorator(Coffee* c) : CoffeeDecorator(c) {}
void describe() const override {
CoffeeDecorator::describe();
std::cout << " + Milk";
}
};
class SugarDecorator : public CoffeeDecorator {
public:
SugarDecorator(Coffee* c) : CoffeeDecorator(c) {}
void describe() const override {
CoffeeDecorator::describe();
std::cout << " + Sugar";
}
};
// Driver code
int main() {
// Creating a simple coffee
Coffee* myCoffee = new SimpleCoffee();
// Adding the Milk decorator
myCoffee = new MilkDecorator(myCoffee);
// Adding the Sugar decorator
myCoffee = new SugarDecorator(myCoffee);
// Output the final description
myCoffee->describe();
delete myCoffee;
return 0;
}

The Decorator Pattern is commonly used when we need to dynamically add or modify objects' behavior at runtime without altering their structure. Common use cases include:

  • Extending UI components: Used to extend the functionality of user interface components in GUI frameworks.

    • Example: Adding scrollbars, borders, or custom colors to window components dynamically in a graphical application.

  • Enhancing I/O streams: Adding responsibilities like buffering, encryption, or compression to I/O streams.

    • Example: Decorating a file input stream with a buffer to improve read performance.

  • Dynamic behavior modifications: Enhancing methods with additional behaviors such as logging, authentication, or validation.

    • Example: Wrapping service calls with decorators to add logging, error handling, or pre-processing.

  • Flexible object customization: Providing alternatives to subclassing for adding features to objects in a modular way.

    • Example: Adding various toppings to a pizza object without creating a complex subclass hierarchy.

Pause and Ponder: How does the Decorator Pattern enhance the functionality of objects in software design?

Conclusion#

Preparing for design patterns interviews is not just about memorizing concepts; it's about understanding how to apply these patterns effectively in real-world scenarios. As you continue your journey in software development, the integration of these patterns into your coding practices will not only help you in interviews but also in creating high-quality, professional code. By getting familiarized with common interview questions, class diagrams, and code implementations, you’ll be better equipped to demonstrate your knowledge during the interview process.

In addition to preparing for design pattern interviews, if you’re getting ready for coding, system design, or architecture interviews, be sure to explore the following courses. They provide detailed content on these topics, along with practical challenges to help you improve your skills, ultimately increasing your chances of success in all interview rounds.

Frequently Asked Questions

What are the essential design patterns every developer should know?

Some must-know design patterns include:

  1. Singleton: Ensures a class has only one instance.
  2. Factory: Creates objects without exposing the instantiation logic.
  3. Observer: Allows objects to be notified of state changes in other objects.
  4. Strategy: Enables selecting algorithms at runtime.
  5. Decorator: Adds behavior to objects dynamically.
  6. Adapter: Converts one interface to another, making classes compatible.
  7. Facade: Simplifies complex systems by providing a unified interface.
  8. Command: Encapsulates requests as objects, enabling undoable operations.

What is Gang of Four (GOF)?

How to select a design pattern?

What are the benefits of Dependency Injection (DI)?

How does the Observer Pattern differ from the Publish-Subscribe Pattern, and when should you use each?

When should you choose abstract classes over interfaces in Java?

Is MVC a design pattern?

What is a Service Locator?


Written By:
Muhammad Hamza
Join 2.5 million developers at
Explore the catalog

Free Resources