Interviewers looking for an experienced hire usually do not ask candidates about their familiarity with a particular language’s syntax. Instead, they ask questions that test the candidate’s skill and experience with software design patterns.
Software design questions demonstrate your critical thinking, problem-solving skills, and ability to optimize or scale a given problem. Learning how to effectively deploy design patterns empowers you to answer any coding challenge.
In this article, you will take a look at what design patterns are and learn how to implement a few design patterns by solving real-world challenges you’re likely to face in an interview.
Today we will cover:
Learn how to effectively deploy design patterns to any coding interview question
JavaScript Design Patterns for Coding Interviews
A design pattern in software engineering is a general, reusable solution to a commonly occurring problem, such as building JavaScript web applications or designing a common computer system.
Instead of being a ready-to-use solution, a design pattern provides a description or template for how to solve a problem, with applications for varying situations. These patterns allow us to reuse code structure for specific system design problems.
The illustration above shows an example of a commonly used design pattern: the observer pattern. This is useful in systems where changes to an entity (the publisher) can affect other dependent objects (the subscribers), but the dependents have no control over when or how these changes occur. Using a design pattern has many advantages:
For a senior developer coding interview, design questions will be baked into the coding questions you face. There is no separate design interview, so you should be prepared to face system design questions at any stage.
There are many types of design patterns, as there are many types of problems that we need to solve. Design patterns in JavaScript generally fall under four categories:
Structural Design Patterns concern class/object composition and relationships between objects. They let you add new functionalities to objects so that restructuring some parts of the system does not affect the rest. Hence, the entire system does not need to change when some parts of structure change. In short, structural patterns are best for object composition, or how different objects can use each other. Some common types of structural patterns are adapter, composite, bridge, decorator, flyweight, proxy, and facade pattern.
Behavioral Design Patterns are concerned with communication between dissimilar objects in a system. Like in the observer pattern we saw above, these patterns streamline communication and make sure the information is synchronized between such objects. In short, behavioral design patterns are concerns with the responsibilities between objects by outlining the patterns for communication between objects. Some common types of behavioral patterns are chain of responsibility, observer, strategy, template method, command, iterator, memento, and state.
Architectural Design Pattern a general, reusable solution to a commonly occurring problem in software architecture within a given context. While they may look like design patterns, architectural design patterns have a broader scope. Some common examples include MVC, MVP, and MVVM.
As you can see, these different categories of design patterns are useful for different scenarios, challenges, and system requirements. As we continue in this article, we will be taking a closer look at these design patterns with real-world examples. We’ll also practice some of these patterns so you can confidently answer any questions related to them in an interview. Let’s get started and practice with some challenges.
The creational design pattern provides optimized object creation techniques. They are useful in a number of scenarios, such as when:
A common example in this category is the Factory Pattern. This pattern provides a template that can be used to create objects and is used in complex situations where the type of the object required varies and needs to be specified in each case. In JavaScript, it does not use the new
keyword to directly create objects, meaning that it provides a generic interface for delegation.
Let’s see how one general factory template can be used to create different objects with similar characteristics.
Consider a real-life ice cream factory, which acts as a template to produce different types of ice cream according to the requirements:
class IceCreamFactory {constructor() {this.createIcecream = function(flavor) {let iceCream;if (flavor === 'chocolate'){iceCream = new Chocolate();}else if (flavor === 'mint'){iceCream = new Mint();}else if (flavor === 'strawberry'){iceCream = new Strawberry();}return iceCream;};}}class Chocolate {constructor() {this.icecreamFlavor = "chocolate";this.message = function() {return `You chose the ${this.icecreamFlavor} flavor.`;};}}class Mint {constructor() {this.icecreamFlavor = "mint";this.message = function() {return `You chose the ${this.icecreamFlavor} flavor.`;};}}class Strawberry{constructor() {this.icecreamFlavor = "strawberry";this.message = function() {return `You chose the ${this.icecreamFlavor} flavor.`;};}}// creating objectsconst iceCreamfactory = new IceCreamFactory();const chocolate = iceCreamfactory.createIcecream('chocolate');const mint = iceCreamfactory.createIcecream('mint');const strawberry = iceCreamfactory.createIcecream('strawberry');console.log(chocolate.message());console.log(mint.message());console.log(strawberry.message());
Here, we created a factory and named it IceCreamFactory
. Think of this as the generic template that we use to make different types of ice cream. Its constructor has a function createIcecream
to accept the parameter flavor
. Depending on the flavor
, it instantiates an object of the corresponding class (see line 6 for example). Now that we understand how this looks in JavaScript code, let’s try an example ourselves.
In this problem, we will be implementing another factory called ToyFactory
. It should be able to create the toys duck
and car
using either the ToyDuck
or ToyCar
function constructor. The ToyDuck
should have the following properties: color
& price
. The ToyCar
should have the following properties: color
, price
, and name
.
So, let’s first create a function: createToy
. This should decide which toy to create depending on the parameter we pass to it. Try it yourself below and then click on the tab to see the solution. We will discuss the solution piece by piece.
function ToyFactory() {this.toy = ToyDuck; //toy property set to ToyDuck by default}function ToyDuck() {} //define the ToyDuck classfunction ToyCar() {} //define the ToyCar class
Alright, so we wanted to create the function createToy
that would make either a ToyCar
or ToyDuck
. In the solution, we started by defining these constructor functions:
ToyDuck()
accepts a parameter toyObj
. The object sets its color equal to toyObj.color
and its price equal to toyObj.price
, as we define them to be.ToyCar()
accepts a parameter toyObj
. The object sets its color equal to toyObj.color
, its price equal to toyObj.price
, and its name equal to toyObj.name
, as we define them to be.With the constructor functions defined, we implement the factory pattern by using the createToy
method which is defined inside the ToyFactory
constructor function. The createToy
method accepts a parameter toyChosen
. This parameter has a property, toyType,
which stores the type of the toy. Depending on its value, it sets the value of toy equal to either ToyDuck
class or ToyCar
class. The function’s return value is an object instance of the required class. Let’s look at that in code:
function ToyFactory() {
this.toy = ToyDuck;
this.createToy = function(toyChosen) {
if (toyChosen.toyType == "duck") {
this.toy = ToyDuck;
} else if (toyChosen.toyType == "car") {
this.toy = ToyCar;
}
return new this.toy(toyChosen);
}
}
Way to go! Now you should understand the basics of creational patterns, named the factory pattern, which is very common for system design questions. As I mentioned before, other common patterns include the Constructor Pattern, the Singleton Pattern, the Builder Pattern, the Prototype Pattern, and the Abstract Pattern.
Next, we will take a look at Structural Patterns and practice solving a coding challenge using one of the patterns.
As we previously discussed, these patterns deal with object relationships and the structure of objects. With structural patterns, you can add functionalities to objects so that restructuring some parts of the system does not affect the rest. Structural patterns ease the process of system design and allow for flexibility when assembling large structures from objects or classes. We will be looking at the decorator pattern as an example of structural patterns in this section.
The Decorator Pattern is a structural pattern that allows behavior to be added to an individual object dynamically without affecting the behavior of other objects from the same class. It’s useful in applications that have many distinct objects with the same underlying code. Instead of creating all of them using different subclasses, additional functionalities can be added to the objects using the decorator pattern.
Let’s take a look at this in code to better grasp the concept.
class Text {constructor(value) {this.value = value;}}function applyHeadingStyles(text) {text.color = "gray";text.size = "18px";return text;}function changeFont(text, font) {text.font = font;return text;}const text = new Text("Hello world.");const heading = applyHeadingStyles(text);const textWithFont = changeFont(text, "Arial");console.log(heading);console.log(textWithFont);
This is a simple example using text formatting. Here, we create a new Text
object with a value property. Next, we have two decorators which can change the appearance of the text, either by applying a set of styles to make it appear as a heading or by changing its font. These decorators can be applied later but are not needed in the creation of the text. Now, let’s look at another example.
The code below implements the functionality to customize superheroes for a game. Notice that every superhero has their own power, and you can create superheroes that have these additional powers, but observe that a superhero can only have one of these available customizations:
Your task is to modify the code to create the option to add multiple customizations to a single superhero object. Your output will be a message displaying multiple superpowers per hero. Try it out yourself before checking the solution in the tabs.
class SuperHero {constructor(name,power) {this.name = namethis.power = power}}class SuperHeroWithSword extends SuperHero{constructor(name,power){super(name,power)this.sword = true}hasSword(){return `${this.name}'s power is ${this.power}, and he also has a sword now.`}}class SuperHeroWithSuperSpeed extends SuperHero{constructor(name,power){super(name,power)this.superSpeed = true}hasSuperSpeed(){return `${this.name}'s power is ${this.power}, and he also has the super speed now.`}}class SuperHeroWithSpeedandSword extends SuperHero{constructor(name,power){super(name,power)this.speedAndSword = true}hasSpeedAndSword(){return `${this.name}'s power is ${this.power}, and he also has both super speed and a sword now.`}}unction ToyCar() {} //define the ToyCar class
The original code implements inheritance to create a customized character. There are three subclasses that inherit the properties of the parent class, SuperHero
, and extend its functionality by initializing an additional property and method in their definitions. Since each customization is a different class, a superhero can only have one customization at a time.
class SuperHero {
constructor(name,power) {
this.name = name
this.power = power
}
}
Utilizing the decorator pattern, we make each class a function that takes the SuperHero
object as a parameter, adds new properties/methods to it, and returns it. This change makes it possible to add various customizations to a single SuperHero
instance, removing the need for extra classes/subclasses.
We use the extends
keyword to extend the functionality. Now, the child classes can inherit the properties of the parent class and initialize an additional property and method in their definitions. Look at the modifications for each child class according to the decorator pattern.
function SuperHeroWithSword(superhero){
superhero.sword = true
superhero.hasSword= function(){/*code*/}
return superhero;
}
function SuperHeroWithSuperSpeed(superhero) {
superhero.superSpeed = true
superhero.hasSuperSpeed= function(){/*code*/}
return superhero;
}
function SuperHeroWithSpeedandSword(superhero){
superhero.speedAndSword = true
superhero.hasSpeedAndSword = function() {/*code*/}
return superhero;
}
Each class
becomes a function
that takes the SuperHero
object as a parameter. Now it is possible to add multiple customizations to a superhero.
var superhero1 = new SuperHero("Fire Man", "Fire")
SuperHeroWithSword(superhero1)
SuperHeroWithSuperSpeed(superhero1)
Learn the other JavaScript design patterns without scrubbing through videos or documentation. Educative’s text-based courses are easy to skim and feature live coding environments, making learning quick and efficient.
Behavioral patterns deal with communication between different objects in a system. They also assign responsibilities to objects and ensure that they all have synchronized information. There are many types of behavioral patterns, and today we will cover the Chain of Responsibility pattern in this section, along with a coding challenge to get you started with this common design pattern challenge.
The chain of responsibility pattern allows a request sent by a client to be received by more than one object. It will create a chain of loosely-coupled objects that either handle a request or pass it to the next object. A common example of this pattern is event bubbling in DOM.
Let’s try to implement the chain of responsibility design pattern by solving the challenge in the next section.
In this challenge, you’ll use the chain of responsibility pattern to assign work to employees. Each employee has a name and a level property. The level of an employee tells which task they can handle, i.e easy, medium, or hard. And each employee is assigned a task depending on that level. In this example, you have been provided an abstract class EmployeeChain
that contains two functions:
setNextEmp
: sets the next object in the chainassignWork
: assigns work to an employee depending on their levelYou should use the chain of responsibility pattern to implement these functions, so you can achieve the final goal of assigning work to employees. Your output should be a message that displays that the work has been assigned to the appropriate employee. Try it out yourself before looking at the solution. We’ll break down the solution below.
class EmployeeChain{setNextEmp(nextEmpInChain){}assignWork(req){}}
In this challenge, we used the chain of responsibility pattern to assign work to employees. We started by creating an employee that will have both a name
and a level
. Every employee can have one of the three levels: easy, medium, or hard, which, as previously outlined, determine what they can be assigned. The getLevel
and getName
functions return the name
and level
property of an employee.
The next task is to assign work to that employee. For this purpose, we define three classes: EasyLevelWorkHandler
, MediumLevelWorkHandler
, and HardLevelWorkHandler
. These will decide if an employee should be assigned the easy, medium, or hard work. All three will inherit from the EmployeeChain
parent class.
class EmployeeChain
{
setNext(nextInChain){}
assignWork(req){}
}
Each class extends from the EmployeeChain
parent class and has a constructor defined as below. Here, super
initializes the parent methods for the child class. The constructor also initializes the variable nextInChain
, which will be the object next to the current one in the chain. The next step is to create a chain of handlers. For this purpose, we define the setNext
function in each child class.
setNext(nextObj){
this.nextInChain = nextObj;
}
This sets the next object in the chain equal to the object passed as a parameter to the function. So now, we can create a chain of handlers as follows:
var w1 = new EasyLevelWorkHandler();
var w2 = new MediumLevelWorkHandler();
var w3 = new HardLevelWorkHandler();
w1.setNext(w2);
w2.setNext(w3);
We create the handlers w1
, w2
, and w3
. They are connected so that if a request is sent to assign some work to an employee, w1
will check if the level of that employee is Easy
. If it is, it will assign easy work to the employee. Else, it will forward the request to the next handler in the chain. Take a look in the code above for the assignWork
function.
MediumLevelWorkHandler
checks if the level of the employee is Medium
. If not, it forwards the request to the next handler, HardLevelWorkHandler
, which checks if the level of the employee is Hard and assigns the work accordingly.
const emp3 = new Employee("Shawn","Hard")
w1.assignWork(emp3);
So, the work needs to be assigned to emp3
. The first handler in the chain checks if the level of emp3
is Easy
. It’ll forward the request to assign work to the next handler in the chain, MediumLevelWorkHandler
, which will check if the level of emp3
is Medium
. It’ll forward the request to assign work to the next handler in the chain, HardLevelWorkHandler
. This handler will check if the level of emp3
is Hard
. The answer will be true
, so it’ll return a message that hard work has been assigned to Shawn.
Way to go! Now you should understand the chain of responsibility pattern, which is very common for system design questions. As I mentioned before, other common patterns include the Command Pattern, the Mediator Pattern, the Iterator Pattern, the Visitor Pattern, and more.
In the next section, we will take a look at Architectural Patterns and practice solving a coding challenge.
Architectural patterns help build an efficient architecture for an application. Think of these as the source of subsystems with assigned responsibilities, such as guidelines for constructing relationships between subsystems. The MVC Pattern, which stands for model-view-controller pattern, is a very popular architectural pattern for web development that we can use to organize code by splitting application functionality into components. The model component manages the application data. The view renders data on the user-facing parts of the application. The controller connects the model and view components.
Angular and Ember are two examples of JavaScript frameworks implementing the MVC pattern in their design. It’s useful when you want to improve application organization in your application or speed up development, since developers can work on different components of the application at the same time. Alright, now let’s try to solve a challenge by implementing the MVC pattern.
In this challenge, you’ll be implementing the MVC pattern. It should display the items in a shopping cart, which will also be your output. You have three components:
ShoppingCartModel
: The shopping cart model should have itemNumber
, itemName
, itemQuantity
, and itemPrice
.
ShoppingCartView
: You are already given the part of the code that initializes the current view for the controller. Implement a buyItem
function to buy an item. A changeItemQuantity
function updates the quantity of an item in the cart. It should take parameters itemNumber
and newQuantity
.
ShoppingCartController
: The controller should update the view whenever a change occurs in the shopping cart model or if a user edits the view.
Try it yourself before looking at the solution, which we will break down below.
class ShoppingCartModel{//write code here}class ShoppingCartView{constructor(){this.controller = null;}registerWith(controller) {this.controller = controller;this.controller.addView(this);}displayItem(itemNumber,itemName,itemQuantity,itemPrice){console.log(`Item Number: ${itemNumber}\nItem: ${itemName}\nQuantity: ${itemQuantity}\nPrice: ${itemPrice}`);}//write code here}class ShoppingCartController{constructor(){this.model = null;this.view = null;this.itemList = [];}addView(view) {this.view = view;}addModel(model) {this.model = model;}//write code here}
The program will display information about the items in our shopping cart and is divided into three components:
The shopping cart object is represented by the model ShoppingCartModel
.
The information about the items in the cart is displayed by the view component ShoppingCartView
.
The model and view components are connected through the controller ShoppingCartController
.
Let’s take a look at each component and see how it works piece-by-piece.
ShoppingCartModel
:
This presents a shopping cart object which has the following properties: the item number, name, quantity, and price. It also has get
functions to retrieve the values of these properties.
ShoppingCartView
: The view displays the data. In this case, that is the cart information on the user interface. It must show the updated information if a change occurs in the model or if a user makes an edit in the view when interacting with the interface. The controller is like the mediator between the model and view. So, for the controller must communicate with the view, we initialize the current view for the controller by setting the controller property and adding the view:
registerWith(controller) {
this.controller = controller;
this.controller.addView(this);
}
Say a user makes an edit in the view. Maybe a person buys an item and invokes the buyItem function in view. Now that there has been an edit in the view, the controller must register the user action and make changes on the model side.
buyItem(itemNumber,itemName, itemQuantity, itemPrice) {
this.controller.buyItem(itemNumber,itemName, itemQuantity, itemPrice);
}
Since the view needs to display our information, it also uses a displayItem
function to display the itemNumber
, name
, quantity
, and price
of any item.
ShoppingCartController
: The controller uses the methods addView
and addModel
to initialize the model and view components. It also initializes the property itemList
. The controller uses its own buyItem
function that it calls when a user calls the buyItem
function on the view side.
buyItem(itemName, itemQuantity, itemPrice){
this.itemList.push(new ShoppingCartModel(this.itemList.length,itemName, itemQuantity, itemPrice));
this.updateView();
}
The function creates a new instance of the ShoppingCartModel
, passing it the information of the item to buy. It then adds it into the itemList
. With this update, the controller updates the view.
updateView()
{
for( let i in this.itemList)
this.view.displayItem(this.itemList[i].getItemNumber(),this.itemList[i].getItemName(), this.itemList[i].getItemQuantity(), this.itemList[i].getItemPrice());
}
Next, the controller also has a setItemQuantity
function. Whenever the data (quantity of item) in the shopping cart model is updated, the setItemQuantity
function must be invoked in the controller. It finds the itemNumber
of the item whose information is to be changed and updates quantity. With this update, the view is also updated using the updateView
function.
Way to go! Now you should understand the basics of the four main JavaScript design patterns. For architectural patterns, you’ll also need to cover the MVP and MVVM patterns. That wraps up our introduction to design patterns. You should have a fairly good idea what the four types of design patterns are. But there is more to learn!
Not only do we need to keep practicing and understanding these patterns, but we need to learn how to apply them when we actually are given a system design question. So, we will improve on what we have learned by reviewing a 7-step approach to solving design problems in the next step. Let’s jump in!
Having good knowledge of software design patterns can be very useful in tackling coding questions in interviews. You may also be required to tackle a peculiar problem, for example, how you would go about designing a fast and scalable system like Twitter.
Fahim ul Haq, CEO of Educative and former senior software engineer, recommends this 7-step framework to approaching system design problems in interviews.
This is the first and most important step when answering an interview question. You should be able to ask the right questions in order to understand the scope of the problem. Since design interviews do not have one right answer.
Asking these questions will help you narrow down the system’s requirements and give you a clearer picture. Candidates who spend time in clearly defining the end goals of the system, always have a better chance of success.
With the system’s requirements narrowed down, you can now describe or identify the APIs exposed by the system. Doing this will ensure that you got the system requirements in the previous step right.
In this step, you have to answer questions about the system’s scale. You should have a good estimate for what your system’s requirements for storage, network bandwidth and read/write frequency would be.
This estimate will help you later when you’ll be focusing on scaling, partitioning, load balancing and caching.
Having a good understanding of a system’s data model is crucial to a good system design. You should be able to answer questions about data storage, encryption and what database system to use.
Next, you need to provide an overview of the system using a diagram that identifies its core components. You should identify enough components that are needed to solve the actual problem from end-to-end. I’ve provided a rough sketch as an example below:
You should be able to explain how you would design 2-3 components in detail, especially discussing what approach you would employ to solve a particular problem. Typically, you would be talking about caching, load balancing, maintaining data integrity, etc.
While going through your system’s design, try to discuss as many potential bottlenecks as possible. Talk about how you would set up performance monitoring, and mitigate against total shutdown of your application if a few components stop working as expected.
Congratulations! As with every other discipline, you can only get better at identifying and implementing design patterns in JavaScript by practicing frequently. With a firm grasp of these patterns, you will fare better in future interviews. Consistent practice is the key!
To get you started, I recommend Educative’s course JavaScript Design Patterns for Coding Interviews, which will adequately prepare you for all the aspects of an interview process.
This course is designed around the main design patterns. By the end of this course, you’ll confidently use design patterns to tackle advanced coding interview questions!
Free Resources