OOPs and System Design
OOPs and System Design
Object-Oriented Programming (OOP) is a way of writing and organizing code in modern programming
languages like C++, Java, Python, PHP, C#, and more. Its main focus is around classes and objects, which
allow us to structure programs in a way that mirrors real-world things and ideas.
1. Classes: A blueprint or template for creating objects. Think of a class like a blueprint for a house—you
can create multiple houses (objects) from the same blueprint.
2. Objects: Instances created from a class. If the class is a blueprint for a car, the actual car built from
that blueprint is an object.
Real-World Example:
Each car (object) is built from the car blueprint (class), but they can have different colors, engine sizes, etc.
The goal of OOP is to combine data (like variables) and functions (actions or methods) that work on that data
into a single unit, called an object. This makes the code safer and easier to manage. Only the specific
functions in that object can change the data, so other parts of the code can’t mess with it.
Example:
● A car’s speed might be private data inside the object, and only a specific function like accelerate()
or brake() can change the speed.
● This prevents anyone from accidentally changing the car’s speed without using the proper methods.
● Encapsulation: Protects the data by keeping it inside the object and only allowing specific functions to
access it.
● Inheritance: Allows one class to inherit features (like attributes and methods) from another class,
making code reuse easier.
● Polymorphism: Lets objects of different classes be treated as objects of a common class (e.g., both
cars and bikes are vehicles but behave differently).
● Abstraction: Simplifies complex systems by hiding unnecessary details and showing only the essential
features.
In simple terms, OOP organizes code to make it more secure, reusable, and easy to manage, just like how
real-world objects (like cars or phones) have specific behaviors and properties that are protected from random
changes.
"The goal of OOP is to combine data (like variables) and functions (actions or methods) that work on
that data into a single unit, called an object."
OOP combines data (variables) and functions (methods) into a single unit called an object.
● Data (Variables): Characteristics of the object, e.g., a car's color, speed, and fuel level.
● Functions (Methods): Actions the object can perform, e.g., accelerate, brake, refuel.
● Object: A combination of data and functions. A car object includes both its properties and actions.
By combining these into one object, the car's behavior and data are grouped together, making it easier to
manage and protect. It helps organize code and ensures that data is modified only through specific actions.
In this example, the Car class combines both the data (like color, speed, fuel level) and the functions (like
accelerate, brake, refuel) into one object. This encapsulation of data and methods in a single unit makes the
object easy to manage, and the data is only modified through specific functions, maintaining safety and control
over how the car’s state changes.
1. Encapsulation:
Encapsulation is a key principle in OOP that helps protect the data and ensure security by hiding the internal
state of the object and only allowing it to be changed through specific methods.
In the example code I provided earlier, the data members (color, speed, and fuelLevel) are private,
meaning they cannot be directly accessed or modified from outside the Car class. This ensures that the
internal state of the car (e.g., its speed or fuel level) cannot be accidentally or maliciously altered by other parts
of the program.
1. Data Protection:
○ By making the data private, you ensure that only the class’s methods can modify the data. In
the example, speed and fuelLevel can only be changed using the accelerate(),
brake(), or refuel() methods.
○ This ensures that the car’s speed doesn’t accidentally get set to a negative number or some
invalid value, as it is controlled by the class logic.
2. Controlled Access:
○ Only methods in the class can change the data. So if a program wants to change the car’s
speed, it has to do so in a controlled way through accelerate() or brake() methods.
○This allows you to validate the input before changing the data. For example, in the brake()
function, if the speed goes below zero, we set it back to zero. This prevents the car from having
an invalid speed like -10.
3. Prevents Unexpected Behavior:
○ Imagine if you could modify the car’s fuel level directly without checks. You could accidentally
set the fuel level to something nonsensical, like 200%, which would break the logic of your
program.
○ In the refuel() method, we make sure the fuel level stays within a valid range (0% to 100%).
If you allow any random part of the program to change the fuel level directly, it can introduce
bugs or security vulnerabilities.
2. Abstraction:
Encapsulation also ties into abstraction, where you expose only the necessary details (the methods) and hide
the internal implementation from the outside world. In the Car class example:
● You don't need to know how the car increases speed or how the fuel system works. You just call the
accelerate() or refuel() method.
● This prevents external code from messing with the internal logic of the class.
Conclusion:
The reason OOP is secure is because of encapsulation—which keeps data hidden and only allows controlled
access through specific methods. This keeps the internal state of an object safe from unexpected changes and
makes the program more robust and less prone to errors.
If anyone tries to bypass these rules (like directly changing the speed), the compiler throws an error. This
ensures that data is protected and only modified in a controlled manner.
When we say "data is kept inside the object", we are really referring to the fact that the class defines the
structure and behavior (data and methods), and when we create an object from that class, the actual data is
stored inside that object.
1. Class:
○ Acts as a blueprint that defines the structure (data members) and behavior (methods).
○ Data members are defined but don’t hold actual values yet.
2. Object:
○ When an object is created, it gets its own copy of the data members defined in the class.
○ The data (specific values) is stored inside the object, not the class.
1. Data Protection:
○ The object stores the data safely and only allows changes via controlled methods.
2. Object-Specific Data:
○ Each object maintains its own data, ensuring independence and flexibility.
3. Organized Code:
○ Grouping data and methods within an object simplifies management and prevents unintended
interference.
A class is like a blueprint for creating objects. Think of it as a template that describes what properties
(variables) and behaviors (functions) an object will have. It doesn't take up any memory itself until you actually
create an object from it.
Objects Interaction:
1. Static Allocation:
Key Differences:
● Static Object: Memory is automatically managed; object is destroyed when it goes out of scope.
● Dynamic Object: Memory must be explicitly deallocated using delete.
delete carPtr;
1. Abstraction
In programming, abstraction hides the implementation details and only shows the functionality.
2. Inheritance
3. Polymorphism
4. Encapsulation
Recap:
1. Easier development and maintenance: Dividing code into classes and objects makes large projects
manageable and maintainable.
2. Data hiding and security: Encapsulation keeps sensitive data safe and protected.
3. Solving real-world problems: Objects mirror real-world entities, making it intuitive to model complex
problems.
4. Code reusability: Inheritance allows the reuse of code across different parts of your system.
5. Generic code: Polymorphism lets you write methods that work for different types of data, reducing
repetition.
In conclusion, OOP makes programming more efficient, organized, secure, and scalable, which is
essential for building complex and robust applications.
When you create an object dynamically in C++ using the new keyword:
"You need to have a class before you can create an object. When a class is defined, no memory is
allocated, but memory is allocated when it is instantiated (i.e., an object is created)."
In Object-Oriented Programming (OOP), Access Specifiers control how the members (data and methods) of
a class are accessed. They determine whether the data or functions of a class can be used directly by code
outside the class or from other classes.
Access Specifiers help enforce the principle of encapsulation, which keeps sensitive data hidden from
unauthorized access and defines how objects interact with each other.
1. Public
2. Private
3. Protected
1. Public:
2. Private:
3. Protected:
○ Inheritance: Allows classes to inherit properties and behaviors from other classes.
○ Encapsulation: Combines data and methods into a single unit and restricts access to some of
the object's components.
○ Polymorphism: Allows objects to be treated as instances of their parent class. It provides the
ability to call the correct method depending on the object.
○ Data Abstraction: Provides only essential information to the outside world and hides the
background details.
When to Use:
A constructor is a special member function of a class that is called automatically when an object is created. It
is used to initialize the object's data members. There are three types of constructors in C++:
Types of Constructors:
1. Default Constructor:
a. A constructor that doesn't take any arguments.
b. If not explicitly defined, the compiler provides a default constructor.
2. Parameterized Constructor:
● Takes parameters to initialize an object with specific values.
3. Copy Constructor:
a. A constructor that initializes an object by copying another object of the same class.
b. The copy constructor is invoked when an object is initialized using another object.
Copy Constructor Flow
A copy constructor is a special constructor in C++ that is used to create a new object as a copy of an existing
object. The flow of a copy constructor involves the following key steps:
In object-oriented programming, when copying an object, a shallow copy copies the object's fields as-is,
including pointers or references to the same memory addresses. A deep copy, on the other hand, creates a
completely independent copy of the object, including the memory that the object references, so the new object
is a separate entity in memory.
A shallow copy is the default behavior when copying objects. It copies the values of data members, including
pointers. If the data members are pointers, both the original and the copied objects point to the same memory
location. This can lead to issues like double-free errors.
Explanation:
Problem:
A deep copy creates a new memory allocation for the copied object. This ensures that both objects have their
own separate memory, and changes to one object do not affect the other.
Explanation:
Summary:
● Use a shallow copy when your class does not manage dynamic memory (i.e., doesn't use pointers).
● Use a deep copy when your class uses dynamic memory allocation to avoid issues like double-free or
unintended data sharing between objects.
Constructor Overloading in C++
Constructor overloading allows a class to have multiple constructors, each with a different set of parameters.
The compiler differentiates these constructors based on the number and type of parameters passed when
creating an object. This allows objects to be initialized in different ways, depending on the available
information.
Key Points:
1. Overloaded constructors must have different signatures (different numbers or types of parameters).
2. The appropriate constructor is invoked based on the arguments provided when an object is created.
3. Overloaded constructors enable flexibility in object initialization, depending on the needs of the
application.
In the example below, the smartphone class has three constructors with varying numbers of parameters:
Explanation:
● Constructor with 0 parameters: Initializes the object with default values (unknown, 0, false).
● Constructor with 2 parameters: Initializes the model and _5g_supported fields while leaving
year_of_manufacture with a default value (0).
● Constructor with 3 parameters: Fully initializes the object with all the provided values for model,
year_of_manufacture, and _5g_supported.
Key Characteristics of
Destructors:
● A destructor's name is the same as
the class name, prefixed by a tilde (~).
● There can only be one destructor
in a class, and it cannot be overloaded.
● Destructors do not accept
parameters and have no return type.
● They are typically declared in the
public section of the class.
● If a destructor is not explicitly
defined, the C++ compiler provides a
default destructor.
Destructor Rules:
1. Tilde Symbol: The name of the destructor should begin with a tilde (~).
2. One Destructor: There can be only one destructor in a class, and it cannot be overloaded.
3. No Parameters: Destructors cannot take any parameters.
4. No Return Type: Destructors do not have any return type, not even void.
5. Automatic Invocation: Destructors are automatically called by the compiler, and programmers cannot
call them directly.
6. Compiler-Generated Destructor: If you don't define a destructor, the compiler automatically generates
a default one, which performs a shallow cleanup (destroying non-dynamically allocated members).
● If an object is created using new or dynamically allocates memory in its constructor, the destructor
should use delete to free that memory when the object is destroyed.
● Constructors and destructors do not have a return type, not even void. They are special member
functions whose purpose is to initialize and clean up resources for an object.
Explanation:
● In the set_details
method, both the local variables
(model, year_of_manufacture)
and the instance variables of the
mobile class share the same
name.
● The this pointer is used to distinguish between the local variables and the instance variables. For
example, this->model refers to the instance variable model of the current object, while model
(without this) refers to the local parameter.
● The this pointer is implicitly available in every member function and points to the object that invoked
the method. It is necessary to avoid ambiguity and to make clear that the assignment is intended for the
instance variables, not the local parameters.
Explanation:
Explanation:
Output:
yaml
Copy code
Summary:
1. Method Chaining: You return the current object (*this) from a method so you can call multiple methods on the
same object in a single statement.
2. Passing the Current Object: You pass the current object to another method or function by dereferencing the
this pointer (*this), which allows the function to operate on the invoking object.
In C++, whenever an object calls a member function, it implicitly passes its own address (reference to itself) to that
function using the this pointer. This this pointer holds the memory address of the current object and allows the
member function to know which object's data members are being referred to during the execution.
Key Points:
1. Implicit Object Passing: When you call a member function using an object, the object’s address is implicitly
passed to the function through the this pointer. This lets the function know which specific object it is working on,
especially when dealing with multiple objects.
2. Disambiguation: When a member function has local variables with the same name as the class's data members,
there can be ambiguity. The this pointer allows the function to distinguish between the local variables and the
object’s data members by using this-> to explicitly refer to the object's members.
Non-static member functions belong to a specific object of the class. They can access both static and
non-static data members of the class. A non-static member function is invoked using the object of the class,
and the this pointer is automatically passed to it, which points to the calling object.
"The this pointer is available only within the non-static member functions of a class. If the member
function is static, it will be common to all the objects, and hence a single object can’t refer to those
functions independently."
● Non-static Functions: These are called by individual objects. The this pointer helps the function identify the
calling object and access its specific members. Each object has its own copy of non-static data members, and the
this pointer ensures the correct object is referred to.
● Static Functions: These are common to the class and not tied to any specific object. Since static functions do not
operate on individual objects, the this pointer is not needed. Static functions can only operate on static data
members, which are shared by all instances of the class. Therefore, no particular object can invoke a static
function independently, which is why the this pointer isn't available in them.
In C++, when you do not explicitly define a copy constructor or assignment operator, the compiler
automatically provides them. These are known as the default copy constructor and default assignment
operator, and they perform a shallow copy by default.
Implicit Shallow Copy: The default behavior of the compiler-generated copy constructor and assignment
operator leads to a shallow copy unless explicitly overridden by the programmer to perform a deep copy.
Encapsulation in C++:
Encapsulation is a fundamental concept in object-oriented programming (OOP) that combines data and the
methods that manipulate that data into a single entity, typically a class. This provides two major benefits:
Data Hiding: Prevents direct access to the object's internal state from outside the class.
Data Control: Controls how data is accessed or modified using methods.
Key Points:
Private Members: Encapsulated data (variables) that cannot be accessed directly from outside the class.
Public Methods: Functions that provide controlled access to the private members, such as get and set
methods.
Encapsulation ensures that the internal representation of an object is hidden from the outside. Only the
methods of the object can manipulate the data, thus protecting the object from unintended interference or
misuse.
Benefits of Encapsulation:
1. Data Security: By hiding the data, you protect the class’s internal state from being altered directly from
outside, ensuring integrity.
2. Modularization: The implementation details are hidden, allowing you to change how the class operates
internally without affecting other parts of the code.
3. Maintainability: By controlling how the data is accessed and modified, encapsulation leads to better
code organization and reduces complexity.
Abstraction in C++:
Abstraction is one of the core principles of Object-Oriented Programming (OOP), which focuses on showing
only essential details to the user and hiding the internal implementation of functionalities. It simplifies complex
systems by reducing the visible complexity and only presenting the necessary interfaces to interact with an
object.
Key Points:
1. Hides Complexity: The user does not need to know how the functionality is implemented, only how to
use it.
2. Provides Relevant Information: Only the necessary details (like methods and interfaces) are exposed
to the user.
3. Implemented via Classes: In C++, abstraction is implemented using classes and access specifiers
(private, public, protected).
Real-Life Example:
When you send an email, you only see a "Send" button. You don’t know (or need to know) the technical steps
that happen behind the scenes to send the email. This is an example of abstraction where the complex
process of sending data is hidden from the user.
Advantages of Abstraction:
1. Data Security: It restricts direct access to data, ensuring data protection and security.
2. Reduces Complexity: Users interact with the object through a simple interface, without needing to
understand the underlying implementation.
3. Increases Reusability: The same class can be used in different contexts without exposing internal
details.
4. Avoids Duplication: By hiding the implementation, the same functionality can be reused without code
duplication.
● Private: Members declared as private are hidden from the outside world. They can only be accessed
by the member functions of the class.
● Public: Members declared as public can be accessed from outside the class.
Access specifiers help in defining what part of the class is abstracted from the user and what is available for
external use.
Inheritance in C++
Modes of Inheritance:
● Public Inheritance: The public members of the base class remain public in the derived class, and
protected members stay protected.
● Protected Inheritance: The public and protected members of the base class become protected in the
derived class.
● Private Inheritance: The public and protected members of the base class become private in the
derived class.
Benefits of Inheritance:
1. Code Reusability: Inherited classes can reuse the functionality of the base class.
2. Reduced Redundancy: Common features are implemented once in the base class and reused in
derived classes.
3. Easier Maintenance: Changes in the base class automatically propagate to derived classes.
4. Faster Development: New classes can be built on top of existing ones without starting from scratch.
Types of Inheritance:
1. Single Inheritance: One class inherits from one base class.
2. Multiple Inheritance: A class can inherit from more than one base class.
3. Multilevel Inheritance: A derived class acts as a base class for another class.
4. Hierarchical Inheritance: One base class serves as the parent class for multiple derived classes.
5. Hybrid Inheritance: A combination of more than one type of inheritance.
Real-Life Example
Consider a person who embodies different roles at the same time. For example:
In different contexts, the same person exhibits different behaviors or characteristics, which is a practical
illustration of polymorphism.
Types of Polymorphism in C++
There are two main types of polymorphism in C++:
a) Function Overloading
b) Operator Overloading
a) Method Overriding
● Both the parent and child class methods must have the same
name.
● Both methods must have the same parameters (signature).
● It is applicable through inheritance.
Questions:
Explanation
● When you try to call show() inside D::display(), the compiler raises an error because it cannot
determine whether to call B::show() or C::show().
○ Explicit Call Test: Demonstrates how to resolve ambiguity by explicitly specifying which base
class’s method to call.
Explanation
Explanation
Output
Concrete Implementation of show()
Concrete Implementation of show()
Summary
● Virtual functions allow you to call derived class methods through base class pointers, enabling
runtime polymorphism.
● Pure virtual functions make a class abstract and require derived classes to provide implementations,
ensuring that they cannot be instantiated without implementing the required methods.
Output
Explanation
Important Notes
● If the derived class does not provide an implementation for the pure virtual function, it will also be
treated as an abstract class and cannot be instantiated.
● Abstract classes are commonly used in scenarios where you want to define a common interface for
multiple derived classes without providing a complete implementation in the base class.
Explanation
Use Cases
● Operator Overloading: Friend functions are often used in operator overloading to enable operators to
access private members of the classes they operate on.
● Non-Member Functions: When you want to create functions that operate on class objects but do not
logically belong to any particular class.
● Global Function: Can be defined outside any class and still access private members of the
class it is a friend of.
● Member of Another Class: A member function of another class can also be declared as a
friend and can access the private members of the class it is a friend of.
Explanation: Virtual functions are used to achieve runtime polymorphism, while constructors are used for
object initialization. Since constructors must always be called to create an instance of a class, it doesn't make
sense to have a virtual constructor. However, you can use virtual destructors to ensure proper cleanup of
derived class resources when an object is deleted through a base class pointer.
SYSTEM DESIGN
What is System Design? System design is the process of defining different parts or "elements" that make up
a large application or system, organizing how these parts work together to meet specific requirements. Think of
it as creating a blueprint that shows how all the components of an application fit and communicate with each
other to perform a set of tasks effectively.
1. Architecture: The overall structure of the system. It’s like the backbone that connects all parts. We
design the architecture to determine how different modules or components will interact.
2. Modules: These are independent parts of the system that handle specific tasks. For example, in a
shopping website, the payment module, product catalog module, and user account module are
separate modules. Each module has a defined responsibility.
3. Components or Subsystems: These smaller parts are built within the modules to perform more
specific functions. For example, in the payment module, a payment processor and a transaction log
could be separate components.
4. Flow of Data: This refers to how information moves between different parts of the system. For
instance, when a user places an order on an e-commerce site, their order details go from the shopping
cart module to the payment processing module.
5. Business Logic: The set of rules that define how the system behaves based on inputs. For instance,
"If a user adds an item to the cart, reduce the stock quantity by one" is an example of business logic.
This is coded within the modules.
6. Implementation: This is where the actual code or program is written to make the blueprint work as
intended. The implementation brings the design to life.
1. High-Level Design (HLD): This gives a broad view of the system. It includes the overall structure,
showing how the different modules connect but does not go into deep detail.
2. Low-Level Design (LLD): This dives into specific details within each module, including functions and
classes, and explains exactly how each part works.
Example
Imagine designing a social media application. Here’s how the design process would look:
1. Define the architecture: Decide on the major modules—such as user profiles, posts, notifications, and
search.
2. Design modules: Each module handles specific tasks—such as managing user details or showing
recent posts.
3. Set up data flow: Define how information moves from one part to another—for example, from user
posts to followers’ feeds.
4. Implement business logic: Write code for actions like “follow a user” or “like a post.”
5. Implement and test: Create the application based on this structure and ensure it works as expected.
What is Architecture in System Design? In system design, architecture refers to the internal structure of an
application—how it's organized and built from the inside. When developing an application, architecture helps
determine how all its components or "modules" connect and communicate with each other. It’s essentially a
detailed design blueprint that guides how the system will function as a whole.
Monolithic Architecture Monolithic architecture is one of the most straightforward types of system
architecture. In this architecture, the entire application—front end, back end, and data storage—is bundled
together in a single structure. This means all the code for the different components resides in one place,
functioning as a unified application.
1. Frontend: This is the user interface where users interact with the application.
2. Backend: The part of the system that processes user inputs, performs logic, and communicates with
the database.
3. Data Storage: A place to store information, like a database, where user data or application data is
saved.
1. Simplicity: Because all components are in one place, it’s easier to develop, test, and deploy.
2. Easy Integration and Testing: With everything integrated, testing and securing the application are
straightforward.
3. Performance: Monolithic applications can perform faster in certain conditions since all components are
in the same system and there’s no need for network calls between services.
1. Scalability Issues: Scaling a monolithic app is challenging because you can’t scale individual
components independently.
2. Code Complexity: As the application grows, the code becomes complex and harder to manage.
3. Deployment Challenges: If any single component needs an update, the whole application needs
redeployment. This can be time-consuming and lead to downtime.
What is Architecture in System Design? In system design, architecture refers to the internal structure of an
application—how it's organized and built from the inside. When developing an application, architecture helps
determine how all its components or "modules" connect and communicate with each other. It’s essentially a
detailed design blueprint that guides how the system will function as a whole.
Monolithic Architecture Monolithic architecture is one of the most straightforward types of system
architecture. In this architecture, the entire application—front end, back end, and data storage—is bundled
together in a single structure. This means all the code for the different components resides in one place,
functioning as a unified application.
1. Frontend: This is the user interface where users interact with the application.
2. Backend: The part of the system that processes user inputs, performs logic, and communicates with
the database.
3. Data Storage: A place to store information, like a database, where user data or application data is
saved.
1. Simplicity: Because all components are in one place, it’s easier to develop, test, and deploy.
2. Easy Integration and Testing: With everything integrated, testing and securing the application are
straightforward.
3. Performance: Monolithic applications can perform faster in certain conditions since all components are
in the same system and there’s no need for network calls between services.
1. Scalability Issues: Scaling a monolithic app is challenging because you can’t scale individual
components independently.
2. Code Complexity: As the application grows, the code becomes complex and harder to manage.
3. Deployment Challenges: If any single component needs an update, the whole application needs
redeployment. This can be time-consuming and lead to downtime.
Comparison with Distributed System Architecture
In a distributed or "microservices" architecture, different parts of the application are divided into smaller,
independent services. Each service can be deployed, updated, or scaled independently. However, this requires
additional communication between services, which may increase complexity.
Distributed System Overview A distributed system is a collection of independent machines or "nodes" that
work together and communicate over a network to achieve a unified goal. Unlike monolithic systems, where all
modules and components are located in one place, a distributed system has its components spread across
multiple servers or computers. This distribution allows each component to perform specific functions, making
the system scalable and efficient.
● Monolithic System: All modules and components reside in a single system, which makes it easier to
manage but challenging to scale. If any part fails, it could bring down the entire application due to a
single point of failure.
● Distributed System: Components are spread across multiple machines, which communicate over a
network. If one machine fails, others continue to operate, reducing the risk of complete system failure.
1. Scalability: Distributed systems can be scaled horizontally, meaning that more machines can be added
to handle increased workload or functionality. If a task is too large for one machine, it can be divided
among multiple machines to improve processing power.
2. No Single Point of Failure: In a distributed setup, if one node or machine fails, the system remains
operational, as other nodes can take over the workload.
3. Replication: Data is copied across multiple nodes to ensure availability. If a machine fails, data can still
be accessed from another node with a copy, thus reducing the risk of data loss.
1. Improved Reliability: Since the system is not dependent on a single machine, it can continue to
operate even if one node fails.
2. Increased Scalability: More machines can be added to the system as the workload increases,
allowing the system to handle more users or requests.
3. High Availability: Replicating data across multiple nodes ensures data can always be accessed, even
if one server goes offline.
Challenges and Disadvantages of Distributed Systems
1. Complexity in Management: Distributed systems require network management, load balancing, and
synchronization between nodes, which can increase complexity.
2. Security: Each node needs individual security, which can require more resources and oversight.
3. Latency Issues: Since nodes communicate over a network, there can be delays, especially if nodes
are located far apart or are under heavy load.
Distributed systems are widely used in cloud computing, large-scale applications, and global systems where a
high volume of data and concurrent users must be supported. Examples include Google Search, Netflix, and
Amazon.
1. What is a distributed system, and how does it differ from a monolithic system?
2. Explain the concept of "no single point of failure" in distributed systems.
3. What is horizontal scaling, and how does it benefit a distributed system?
4. What are the main advantages of using a distributed system?
5. Describe a challenge associated with managing a distributed system.
What is Latency? Latency in web applications is the total time it takes for a user request to travel to a server,
be processed, and return a response. For example, when you type "facebook.com," a request goes to
Facebook's server, where it is processed, and a response is sent back. The total time for this "round trip" is
called latency.
If we break it down:
Latency comprises:
● Network Delay: Time for the data to travel over the network.
● Compute Delay: Time for the server to process the request.
In monolithic architectures, all components are deployed in a single location, which reduces network delay
since there is minimal communication between separate servers. In distributed architectures, the application
is divided into multiple modules, which are often deployed on separate servers, introducing network delay as
data travels between these modules.
Reducing Latency
1. Caching: Cache data close to the user to avoid unnecessary trips to the main server. For example,
once a response is received, it is stored temporarily in a cache. If a similar request is made, the system
can retrieve the cached data instead of querying the server again.
2. Content Delivery Network (CDN): CDNs host copies of static data (e.g., images, scripts) on multiple
servers worldwide. When a user requests data, it is delivered from the nearest CDN server, reducing
the response time.
3. Server Improvements: Upgrading server hardware or optimizing software can also help decrease
processing time and improve overall response speed.
● CDN: Primarily serves static data and is geographically distributed to ensure that users worldwide can
access content quickly.
● Caching: Stores data closer to where it’s frequently requested, such as on the server itself, reducing
the need to re-process data that has already been computed.
Key Takeaways
What is Throughput? Throughput represents the amount of data processed by a system per unit of time. It
measures the "work volume" or "information flow" through a system. For instance, if someone creates 100
videos in 100 days, their throughput is 1 video per day.
Formally, Throughput = Amount of Data Transmitted per Unit of Time. In network terms, it's often
measured in bits per second (bps).
● Monolithic System: A monolithic system has all components deployed in a single codebase, making it
limited by the resources available to that single server.
● Distributed System: A distributed system divides work across multiple servers or nodes. This
approach allows for horizontal scaling by adding more machines, which can handle tasks in parallel and
increase throughput. This makes distributed systems generally have higher throughput because they
can process more requests without being limited to a single resource.
1. Load Balancing: A load balancer distributes incoming requests across multiple servers, balancing the
load to avoid overloading any single server. This method improves system efficiency and throughput.
2. Caching: By temporarily storing frequently accessed data close to where it’s needed, caching reduces
the need to repeatedly fetch data from the main server, speeding up response times.
3. Content Delivery Network (CDN): CDNs store static data on geographically distributed servers. This
reduces the time needed to retrieve this data, especially for users who are far from the main server.
4. Distributed System Architecture: Distributed systems allow horizontal scaling, which means you can
add more servers to handle additional tasks, thus increasing throughput.
5. Upgrading Hardware and Resources: Upgrading server hardware or increasing processing resources
can improve the rate at which data is processed.
1. High Latency: Latency delays the response time, which can decrease the number of tasks a system
completes per second.
2. Protocol Overhead: Certain communication protocols between servers (like handshakes) may add
overhead, increasing latency and reducing throughput.
3. Network Congestion: Too many simultaneous requests can cause congestion, resulting in slower
response times and decreased throughput.
Key Takeaways
Availability is the degree to which a system remains operational and accessible for users. This concept
becomes particularly relevant when many users try to access a service simultaneously, such as checking
examination results online. For instance, during CBSE 12th-grade result announcements, the website
experiences a massive increase in requests, which can overwhelm a monolithic system if all processes are
handled by a single server.
In a monolithic architecture:
If the system is not designed to handle high availability, it cannot serve users effectively when a failure occurs.
Therefore, availability in monolithic systems is generally limited due to reliance on a single resource.
In a distributed system:
● Components or modules are spread across multiple servers, allowing work to be distributed and
reducing the risk of a single point of failure.
● This fault-tolerant design means that if one server or module fails, others can continue to operate,
improving overall availability.
In distributed systems, we can also create replicas of critical components. For example, multiple servers may
hold copies of essential data or applications, and a load balancer directs requests to available servers. This
redundancy improves fault tolerance, ensuring that users experience minimal downtime.
Fault tolerance directly impacts availability. Systems with higher fault tolerance are more available because
they can continue to serve users even if some components fail.
● Application Redundancy: In systems with multiple application nodes, each application can
independently serve requests. For example, in microservices, each service can run on separate nodes,
allowing services to work in isolation.
● Data Redundancy: Ensures that data storage is spread across multiple locations, especially crucial in
distributed databases where each node stores a copy of the data to provide continuity.
replication and redundancy both refer to strategies used in distributed systems to increase availability, but
they are applied in different ways:
1. Replication:
○ Refers specifically to creating exact copies of data or applications across multiple servers or
nodes in a distributed system.
○ It ensures that even if one server or instance fails, other replicas can continue to provide the
required data or services without interruption.
○ For example, database replication allows multiple copies of the data to be available, which
supports continuity and fault tolerance in case one database server goes down.
2. Redundancy:
○ A broader term that includes replication but also refers to any method that ensures backup
resources are available to handle failures.
○ Redundancy may involve not only replicating data or applications but also adding extra
hardware or system resources to act as backup in case of component failure.
○ This approach is essential in distributed systems to enhance availability, as it prevents single
points of failure by spreading critical functions across multiple, redundant resources.
Key Points
1. What is the primary reason distributed systems have higher availability than monolithic
systems?
2. Explain how replication enhances availability in distributed systems.
3. Describe the concept of a single point of failure and its impact on system availability.
4. What is the difference between application redundancy and data redundancy?
5. How does a load balancer contribute to fault tolerance in distributed systems?
Consistency in distributed systems, especially focusing on how it affects data availability across different
servers or locations.
Key Points
1. Understanding Consistency:
○ Consistency means that the same data should be accessible by all users at any time,
regardless of where they access it.
○ For instance, if you deposit ₹100 in your bank account, it should immediately reflect across all
systems like ATMs and customer care services.
2. Problem of Inconsistency:
○ If data is not updated simultaneously across all systems, users may see outdated or incorrect
data. This issue is common in distributed systems.
○ An example is when someone books a movie ticket in one location (e.g., Delhi), but another
user from a different location (e.g., Pune) might still see the ticket as available if the database
hasn’t synced instantly.
3. Monolithic vs. Distributed Systems:
○ In a monolithic system, everything runs on one server, so consistency is typically high
because there’s no need to sync across servers.
○ In a distributed system, data is spread across multiple servers or locations. Syncing across
servers can lead to a delay, causing temporary inconsistency.
4. Factors Improving Consistency:
○ Network Speed: A faster network reduces sync time, helping data become consistent more
quickly.
○ Stopping Read Operations: Disabling read operations while data updates can prevent users
from reading incorrect data.
○ Physical Proximity of Servers: Placing servers closer to each other can reduce sync time, as
data travels faster over shorter distances.
5. Types of Consistency:
○ Strong Consistency: All reads always return the most recent write. Systems won’t allow read
operations until all replicas are updated. This is critical in systems like booking, where exact
data is required.
○ Eventual Consistency: Reads might temporarily return outdated data, but over time, all
replicas will sync up. This type of consistency is often used in social media, where minor delays
in updates are acceptable.
○ Weak Consistency: Not all replicas need to be updated immediately or consistently. It’s based
on the business requirement and is often used when real-time updates are not crucial.
Example Scenarios:
The CAP theorem explains that in distributed systems, we can only achieve two out of three properties
simultaneously: Consistency (C), Availability (A), and Partition Tolerance (P). Here’s what each means:
1. Consistency (C): Every read receives the most recent write. For example, if two people, A and B, are
booking a movie ticket, they should see the same availability status. If person A sees a seat available,
person B should also see it, ensuring that the system remains consistent.
2. Availability (A): The system should always be accessible for requests. For instance, sites like Google
are almost always available, regardless of when you access them. However, sometimes maintaining
consistency compromises availability, especially when updates are being processed.
3. Partition Tolerance (P): The system should continue to function despite network failures that may
disconnect parts of the system. This is essential in distributed systems, where data is stored across
different servers. If one server goes down, others should handle the load.
CAP Theorem’s Triangle of Choice: According to CAP theorem, you can choose only two of the three
properties:
● Consistency and Availability (no Partition Tolerance): Typically a centralized system where data is
always up-to-date and accessible but doesn’t handle network failures well.
● Consistency and Partition Tolerance (no Availability): Systems where data is consistent and resilient
to network issues but may become inaccessible temporarily.
● Availability and Partition Tolerance (no Consistency): Systems that prioritize uptime and fault
tolerance but may show outdated data, common in social media where exact consistency is not critical.
Examples:
● Banking: Prioritizes consistency over availability to ensure account balances are accurate.
● Social Media: Availability is crucial; slight delays in data synchronization are acceptable.
● Stock Trading: Consistency is prioritized to show accurate stock prices.
Interview Questions
1. Explain the CAP theorem. Why can’t all three properties be achieved at once?
2. Describe a real-world example where availability is more important than consistency.
3. What type of system would prioritize consistency and partition tolerance, and why?
4. How does CAP theorem influence the design of distributed systems?
5. In CAP theorem, which property is most important for a banking application, and why?
In distributed systems, we have multiple servers located in different geographical locations, such as one in
Australia, one in the US, and one in India. Each server operates on its own clock, which may show different
times due to time zone differences. This leads to a significant challenge: determining the sequence of events.
In a monolithic system (a system where all components are interconnected), the time is uniform and events
can be easily sequenced. However, in distributed systems, where events can occur simultaneously across
different servers, it's difficult to ascertain which event happened first.
Physical clocks are insufficient for determining the order of events because they do not account for the inherent
differences in time between the different servers. To address this issue, logical clocks were introduced. The
significance of time in this context is to determine the sequence of events.
In a distributed system, every process (e.g., P1 and P2) maintains its own logical clock, referred to as a
counter. Each time an event occurs within a process, that process increments its counter.
1. Event Occurrence: When an event occurs in a process, it increments its counter. For example, if P1
has a counter value of 0 and an event occurs, the counter is incremented to 1.
2. Message Sending: When a process sends a message, it includes its counter value with the message.
3. Message Receiving: Upon receiving a message, a process takes the maximum value between its own
counter and the counter received in the message, and then increments it by one. This ensures that the
receiving process is aware of the sequence of events in relation to the sender's events.
Example of Counting
● If P1's counter is 2 and it sends a message to P2, P2 will compare its current counter with the one
received from P1.
● If P2’s counter is 1, it will update its counter to max(1, 2) + 1, which will result in 3.
This method effectively allows processes to determine the order of events across distributed systems by
maintaining a logical sequence, even though physical time may differ.
Conclusion
The Lamport logical clock helps in establishing a partial ordering of events in distributed systems. By using
logical clocks, we can deduce which event occurred before another, even when they happen on different
servers at different times.
What is Scalability?
Scalability refers to a system's ability to handle increased loads or requests without significant degradation in
performance. In simple terms, it assesses whether a system can manage a rise in the number of
requests—whether that means keeping the response time low or maintaining it at an acceptable level.
Types of Scalability
There are two primary types of scalability: Vertical Scaling and Horizontal Scaling.
Vertical scaling involves increasing the capacity of a single machine (server) to handle more load. This could
mean adding more RAM, CPU power, or disk space to an existing server.
Example:
● Imagine you have a server with 8 GB of RAM, and your user base grows from 100 to 100,000. To
accommodate this increase, you might upgrade the server to 32 GB of RAM or add a larger SSD for
more storage.
● Single Point of Failure: If the server goes down, the entire system becomes unavailable.
● Resource Limits: There's a physical limit to how much you can upgrade a single machine.
● Higher Costs: Upgrading to higher-quality components can become expensive.
Horizontal scaling involves adding more machines (servers) to the system rather than upgrading existing ones.
This approach distributes the load across multiple servers.
Example:
● If you have a database that requires 100 GB of storage, instead of upgrading one server to hold all that
data, you might set up four servers with 25 GB each. If your needs increase to 125 GB, you simply add
another server.
● No Single Point of Failure: If one server fails, others can continue to handle the load, improving
overall system reliability.
● Flexibility: You can easily add more servers as your requirements grow without hitting a physical limit.
● Cost-Effectiveness: You can often use less expensive resources since you don't need the highest
quality for each machine.
Conclusion
The choice between vertical and horizontal scaling depends on the specific needs and constraints of your
application and infrastructure. Understanding these two methods helps in designing scalable systems that can
efficiently handle varying loads.
1. Redundancy
Redundancy means having a duplicate copy of a server or node in a system. This duplication ensures that if
one server fails, another is available to handle requests without interrupting the service for users. Redundancy
is a common feature in high-availability systems to make sure they stay up even if some components go down.
● Active Redundancy: Here, all redundant servers are actively running and ready to accept requests.
For instance, if there are three servers in a distributed system, all three can receive requests
simultaneously. The load balancer distributes requests among all active servers, deciding which server
will handle each incoming request. This setup is often used to improve performance since multiple
servers share the load.
● Passive Redundancy: In passive redundancy, only one server is actively handling requests at any
given time, while the others remain in standby mode. If the active server fails, a passive server takes
over, ensuring continuous operation. This type of redundancy is particularly useful when an immediate
switchover is needed without splitting the load across multiple servers.
2. Replication
Replication involves not just duplicating servers but also ensuring synchronization across them, meaning
that the data on all replicated servers is always the same. For example, in a replicated database system, if you
update one server, that change should appear on all other servers as well, so they stay consistent.
Similar to redundancy, replication also has two types:
● Active Replication: In active replication, all replicated servers are synchronized in real-time. This
means that if there are three database servers, any changes made to one database (such as adding a
new record) are immediately mirrored on all other servers. This setup helps in scenarios where
consistent data availability across multiple servers is necessary.
● Passive Replication (Master-Slave): In passive replication, there is a master server and one or more
slave servers. All updates are made on the master server, which then synchronizes those changes with
the slave servers. The master is responsible for all read and write operations, while the slave servers
update themselves based on changes made to the master. If the master server fails, one of the slave
servers is promoted to take over as the master, ensuring continuity.
● Redundancy is simply about duplicating servers to ensure availability if one fails. No synchronization is
required between them.
● Replication adds a layer of synchronization so that data consistency is maintained across all replicated
servers.
In synchronous replication, every update or change made on the master server is immediately copied to the
slave servers before the operation is considered complete. This means that:
● The master waits for confirmation from each slave that it has received and applied the change.
● Only once the change is confirmed across all slaves does the master report the operation as
successful.
● Data Consistency: All nodes (master and slaves) stay synchronized in real-time, ensuring that every
server has the exact same data at any moment.
● High Reliability: If the master server fails, a slave can take over with minimal data loss.
● Latency: Sync replication can be slower since the master has to wait for each slave to confirm the
change before proceeding.
● Network Overhead: Each write operation requires network communication with all slaves, which can
slow down the process, especially with more slaves or longer distances between servers.
Use Case: Sync replication is ideal for critical applications where data accuracy and consistency are essential,
such as financial transactions or e-commerce systems.
● Changes are applied to the master, and the update is queued for the slaves.
● Slaves replicate changes at their own pace, meaning there may be a delay (a lag) before they reflect
the latest data from the master.
● Lower Latency: The master can continue processing other requests immediately without waiting for
slaves, making it faster.
● Scalability: With async replication, the system can handle more slaves without affecting the master’s
performance as much.
● Data Inconsistency: There may be a delay, so slave servers may not have the most up-to-date data. If
the master fails, recent changes might be lost.
● Risk of Data Loss: If the master goes down before changes are synced to slaves, some data can be
lost.
Use Case: Async replication is suited for applications where speed is more important than real-time data
consistency, like content delivery networks (CDNs) or backup systems.
1. Round Robin:
○ Sequentially distributes requests to each server in turn.
2. Weighted Round Robin:
○ Distributes requests based on server capacity. Servers with higher capacity receive more
requests.
3. IP Hash:
○ Maps client IPs to specific servers, ensuring requests from the same client go to the same
server.
4. Least Connections:
○ Routes requests to the server with the fewest active connections, aiming for the lowest
response time.
5. Source IP Hash:
○ Combines client IP and destination address, so each client’s requests are directed to the same
node, ensuring consistency for specific clients.
● Better User Experience: High availability and quick response times due to resource distribution.
● Downtime Prevention: If a server fails, the load balancer directs requests to other servers.
● Scalability and Flexibility: Additional servers can be added, and load balancers distribute workloads
among them effectively.
● Static Algorithms (e.g., Round Robin): Defined in advance; routing doesn’t adapt dynamically.
● Dynamic Algorithms (e.g., Least Connections): Real-time adjustments based on server conditions
and active connections.
This systematic approach ensures system efficiency, reliability, and scalability in distributed environments.
Introduction: In a distributed system, where multiple servers (called nodes) handle requests, load balancing is
a method used to manage and distribute incoming network traffic efficiently. Imagine we have a system where
multiple users send requests, and we have four servers that can process these requests. A load balancer
decides which server (or node) should handle each incoming request. This is crucial to avoid overloading a
single server and to ensure smooth system performance.
Key Concepts:
1. Load Balancing:
○ Load balancing refers to the process of distributing network traffic across all nodes in a system
to ensure efficient handling of requests. This prevents any single server from becoming a
bottleneck due to too many requests, while others remain underutilized.
○ The load balancer decides where each request should go among the available nodes. This
allows the system to provide faster responses and maintain high availability.
2. Health Check of Servers:
○ The load balancer continuously monitors the health of each server. It checks whether each
server is active and capable of handling requests.
○ If a server becomes unresponsive, the load balancer stops sending requests to it and directs
them to a healthy server instead. This ensures high availability and reduces the risk of
downtime.
3. High Scalability and Throughput:
○ Scalability is the system’s ability to handle an increasing number of requests by adding more
servers. With load balancing, additional servers can be easily integrated into the system.
○ This high scalability is essential in distributed systems, as it allows the system to handle more
requests efficiently.
4. Redundancy:
○ Redundancy means having backup servers with the same code and configuration as the main
servers. If one server fails, another identical server can take over, ensuring there is no
interruption in service.
Load balancers use different algorithms to decide how to distribute requests among servers. Here are some
commonly used ones:
1. Round Robin:
○ Requests are distributed in a rotating manner. For example, the first request goes to the first
server, the second to the second server, and so on. After reaching the last server, the cycle
repeats.
○ This method is simple and works well when all servers have equal capacity.
2. Weighted Round Robin:
○ If some servers have more capacity than others, Weighted Round Robin assigns weights to
each server. Servers with higher weights will receive more requests.
○ For instance, if Server A can handle three times as many requests as Server B, then Server A
will get three times more requests than Server B.
3. IP Hash:
○ This algorithm uses the IP address of the client to determine which server will handle the
request. It applies a hash function to the client’s IP, and the output corresponds to a specific
server.
○ This method is useful when each client needs to consistently connect to the same server (e.g.,
for session management).
4. Least Connections:
○ The load balancer directs the request to the server with the least number of active connections.
This is a dynamic approach, as it considers the real-time load on each server.
○ This ensures that servers with fewer connections take on more requests, balancing the load
effectively.
5. Static vs. Dynamic Load Balancing:
○ Static algorithms like Round Robin and IP Hash have predefined rules that don’t change in
real-time.
○ Dynamic algorithms like Least Connections are adaptive and distribute traffic based on the
current load, making them suitable for systems with unpredictable traffic patterns.
Imagine you open a social media app like Instagram. When you click on your profile, you see your bio, profile
picture, followers, following, and posts. Normally, to load all this information, the system fetches data from a
database. This process can be slow if every element (followers, following, posts, etc.) is retrieved each time
you open your profile.
To improve speed, especially when the profile content doesn’t change frequently, caching is used. Caching
temporarily stores data, so the next time you open your profile, the data is quickly loaded from the cache
instead of the database. Since cache often uses faster memory, like RAM, loading from cache is much faster.
For example:
1. The first time you open your Instagram profile, data is loaded from the database and stored in the
cache.
2. For subsequent profile views, the app pulls the information from the cache until there’s an update (like a
new post or a bio change).
Benefits of Caching
1. Reduced Database Load: Caching prevents repeated calls to the database, reducing server load.
2. Faster Response Times: Caching leverages faster storage, improving the app’s performance by
reducing loading time.
3. Cost Efficiency: Fewer database queries mean lower operational costs and faster processing speeds.
Types of Caching
1. In-Memory Cache (Local): Stores data on a single server, accessible only to that server. It’s faster but
limited to individual servers.
2. Distributed Cache: Shared across multiple servers, making it ideal for large applications with several
servers. Tools like Redis and Memcached are commonly used for distributed caching.
● Read-Intensive Applications: News sites like Times of India or Wikipedia, where users mostly view
content.
● Static Content: Unchanging data, like HTML, images, or common API responses, is cached to reduce
load time.
● Content Delivery Networks (CDNs): CDNs cache static assets geographically to deliver them faster to
users around the world.
In systems that use caching to store data, there's a need to manage the storage efficiently. As the cache has a
limited size, some data must be deleted over time to make room for new data. This process is known as cache
eviction. Here’s a structured explanation of the concepts:
Several strategies exist for determining which data to evict from the cache:
Challenges of RDBMS
1. Rigidity:
○ RDBMS requires a predefined structure for tables, making it difficult to change the schema once
established. For example, adding new fields often necessitates altering the entire table
structure.
2. Scalability Issues:
○ RDBMS can face challenges with horizontal scaling (adding more machines). If data is
distributed across multiple tables, managing relationships can become complex. For instance, if
customer and order data are stored across different servers, ensuring consistency across these
tables can be problematic.
3. Complexity in Distributed Environments:
○ When scaling horizontally, managing relationships between tables becomes challenging. If an
order is on one server and the corresponding customer data is on another, it complicates data
retrieval and integrity.
RDBMS primarily stores data in tables. However, RDBMS faces several challenges, such as slow speed and
difficulty in horizontal scaling.
To overcome these challenges, NoSQL databases were introduced. The term "NoSQL" stands for "Not Only
SQL" or "Non-relational" databases. Unlike RDBMS, which is structured, NoSQL databases provide a flexible
schema and are designed to scale easily.
Types of NoSQL Databases: NoSQL is an umbrella term encompassing four main types of databases:
1. Key-Value Stores:
○These databases store data in the form of key-value pairs. A key corresponds to a single value,
making it a simple and efficient way to access data.
○ Example: Redis is a popular key-value store, often used for caching.
2. Document Databases:
○ Document databases store data in documents, typically in JSON or XML format. This approach
allows for a dynamic schema, meaning you can easily add new fields without needing to modify
a predefined table structure.
○ Example: MongoDB is a well-known document database that combines relational concepts with
the flexibility of NoSQL.
3. Columnar Databases:
○ In columnar databases, data is stored in columns rather than rows. This method is
advantageous for analytical queries because it allows for faster data retrieval for specific
columns.
○ Example: These databases are commonly used in data analytics, where querying specific
columns can yield results without scanning entire rows.
4. Graph Databases:
○ Graph databases represent data as nodes and relationships. They excel at storing
interconnected data, making them ideal for applications like social networks, where relationships
between entities are critical.
○ Example: Neo4j is a popular graph database, widely used for social networking applications
and recommendations.
Interview Explanation
When explaining NoSQL databases in an interview, you could frame it like this:
"Traditional relational databases store data in tables with a fixed schema, which can lead to performance
bottlenecks and challenges in scaling. NoSQL databases, which include key-value stores, document stores,
columnar databases, and graph databases, address these limitations. Each type of NoSQL database is
designed for specific use cases, allowing for flexibility and improved performance. For instance, document
databases like MongoDB are great for applications needing dynamic schemas, while graph databases like
Neo4j are perfect for applications that rely on complex relationships, such as social networks."
1. What are the main differences between RDBMS and NoSQL databases?
2. Can you explain how a key-value store works and give an example of its use case?
3. What are the advantages of using a document database over a traditional relational database?
4. Why would you choose a columnar database for data analysis tasks?
5. How do graph databases represent data, and in what scenarios are they particularly useful?
6. What kind of database would you use for a real-time chat application and why?
7. In which situations might you still choose to use an RDBMS over a NoSQL solution?
In this segment, we’re discussing Polyglot Persistence. This concept refers to using multiple types of
databases to handle different parts of an application’s data storage needs. The primary motivation behind this
approach is that a single type of database often cannot efficiently satisfy all the requirements of a complex
application.
Key Points:
Interview Explanation
"Polyglot Persistence is a strategy that allows applications to use multiple database technologies, each
selected for its strengths based on specific use cases. For example, an e-commerce application might use a
key-value store for simple cart operations, a document database for complex order details, an RDBMS for
handling payment transactions, and a graph database to manage customer relationships. This approach
ensures that each component of the application is optimized for its specific data requirements, leading to
improved performance and scalability."
In this segment, we’ll discuss Normalization and Denormalization in database design, essential concepts for
managing data efficiently in relational databases.
What is Normalization?
1. Definition:
○ Normalization is the process of organizing data in a database to reduce redundancy and
improve data integrity. This involves dividing a single table into multiple tables to minimize
duplicate data.
2. Example:
○ Consider an Employee table that contains the following columns: Employee ID, Department ID,
Department Name, and Department Description. If there are 100 employees and only 2
departments, each employee record would repeat the department details, leading to
redundancy.
○ To normalize this, you would keep the Employee table with just the Employee ID and
Department ID, and create a separate Department table with Department ID, Name, and
Description. This reduces redundancy and storage requirements.
What is Denormalization?
1. Definition:
○ Denormalization is the reverse process, where you combine multiple normalized tables into a
single table. This is done to improve read performance and simplify data retrieval, especially
when read operations are more frequent than writes.
2. Example:
○ If you have an Employee table and a Department table, denormalizing would involve merging
them into a single table that includes Employee ID, Department ID, Department Name, and
Department Description. This allows for faster data retrieval, as all relevant data can be
accessed from one table without the need for joins.
Benefits of Denormalization:
Challenges of Normalization:
1. Data Redundancy:
○ While normalization reduces redundancy, denormalization may reintroduce it, which can lead to
larger database sizes.
2. Complexity:
○ Normalized databases can become complex due to multiple tables and relationships, making
them harder to manage.
3. Inconsistency:
○ If the same data is spread across multiple tables, inconsistencies can arise if updates are not
applied uniformly.
4. Slow Write Operations:
○ Denormalization can lead to slower write operations, as multiple places must be updated when
changes are made.
Interview Explanation
"Normalization is a technique used to reduce data redundancy in a relational database by dividing data into
multiple related tables. For instance, in an employee management system, we can separate employee details
from department details to minimize repetition. Denormalization, on the other hand, involves merging these
tables back into a single table to optimize read performance, especially in scenarios where data retrieval is
more frequent than data modification. Each approach has its advantages and challenges, and the choice
between them depends on the specific requirements of the application."
What is Indexing?
Indexing is a method used to optimize the speed of data retrieval operations on a database table. Think of it as
a way to organize your information so that you can find what you need quickly, much like how a well-organized
library allows you to find books easily.
Real-World Analogy
● When you have a table with many records, searching through every entry one by one (like a linear
search) can be time-consuming, especially if there are millions of entries.
● For example, if you have a million records and need to find a specific one, a linear search would require
you to check each record until you find the one you're looking for. This has a time complexity of O(n)
where n is the number of records.
Optimized Searching
● Binary Search: If the records are sorted, you can use binary search, which is much faster and has a
time complexity of O(log n).
● Indexing: When you create an index on a column (e.g., net worth), the database maintains a separate
memory structure that allows for quick lookups. It sorts the values in that column and maintains pointers
to the original rows in the table, significantly speeding up search queries.
Implementation of Indexing
1. Creating an Index: Suppose you have a table of students and you frequently search based on their net
worth. By indexing the net worth column, you create a lookup table that points to the rows in your main
table.
2. Lookup Table Structure: The index will have sorted entries of the net worth and pointers to the
respective rows in the original table. This allows the database to quickly find and return the desired
records with minimal searching.
● Read-Intensive Applications: If your application frequently reads data (e.g., querying user
information), indexing is beneficial as it speeds up these read operations.
● Write-Intensive Applications: If your application involves many write operations (inserts, updates,
deletes), adding indexes might slow down these operations because the index must also be updated
with every change. In such cases, it’s often better not to use indexing.
1. Can you explain how indexing can affect the performance of read vs. write operations in a
database?
2. What might happen if you index too many columns in a database table?
3. How does the use of B-trees in indexing help maintain order and efficiency?
Synchronous Communication: Synchronous communication occurs when a sender and receiver interact in
real-time, meaning the sender must wait for the receiver's response before continuing with other tasks. This
can be likened to making a cash withdrawal at an ATM.
1. Sequential Process:
○ Another way to understand this is through a sequence of events. For instance, consider you
want to eat at a restaurant but first need to withdraw money from the ATM. The process must
follow specific steps:
1. Drive to the ATM.
2. Withdraw cash.
3. Go to the restaurant and order food.
○ Each step must be completed in order; skipping any step (like not withdrawing cash) means you
cannot proceed to the next (eating at the restaurant). This sequential dependency highlights the
nature of synchronous communication.
2. Programming Context:
○ In programming, consider a scenario with three statements:
1. Fetch data from the database.
2. Process that data.
3. Return and print the processed data.
○ These statements will execute sequentially. You cannot start processing data until it has been
fetched, and you cannot print the data until it has been processed. This ensures consistency
and order, which are vital in synchronous communication.
3. Achieving Consistency:
○ Synchronous communication is essential for achieving consistency in systems with multiple
replicas. For instance, if you update a value in a database, that change must be reflected across
all replicas. If one replica is not updated in a timely manner, it may lead to inconsistencies,
where different replicas have different values.
4. High Consistency Needs:
○ Industries like stock markets and banking require high consistency. For example, when you
make a payment, the transaction must ensure that all systems reflect the same state
immediately to avoid issues like double spending.
While synchronous communication is useful for ensuring consistency, it can be limiting in scenarios requiring
responsiveness. In the next video, we'll learn about non-blocking calls, which allow processes to continue
running without waiting for a response, enhancing system performance in distributed environments.
Asynchronous communication allows a process to send a request and then continue executing without waiting
for the response. This is in contrast to synchronous communication, where the process must wait for the
request to be completed before it can proceed.
In this topic, we are focusing on message-based communication, which is a method of data exchange
between systems or services by sending messages back and forth. Let’s break down the main elements:
Testing Questions
A web server is a system that enables web applications to run continuously, ensuring they are always
available to users. This can include both hardware (the physical machine) and software (programs that
manage web requests).
Testing Questions
1. What is the purpose of a web server?
2. Explain the difference between the hardware and software components of a web server.
3. What role does HTTP play in a web server?
4. Give an example of how a browser and web server interact when you visit a website.
5. Name two common web servers and describe what they do.
A web application is an interactive application that operates on the internet. Unlike a website (which is mostly
static and just shows content to users), a web application allows for user interaction. For example, in a website,
content rarely changes unless the owner updates it, like a blog. In contrast, web applications, such as social
media platforms like Facebook, allow all users to interact with and update content.
● Client: This is the user-side device or application that makes requests. Examples include mobile apps
or browsers on a laptop.
● Server: This is the system that responds to the client’s requests by providing data or performing tasks.
For example, when you use Instagram on your phone (the client), your device sends requests to
Instagram’s servers.
Servers can also act as clients when they request data from other servers. This is essential in more complex
systems where different servers may need to interact for information.
REST is a set of guidelines for communication between client and server over the internet:
● SOA: This is an architectural style where an application is divided into different services that can be
used independently or together. SOA helps with reusability and allows selective scaling, so we can
scale only the necessary parts.
● Microservices: This is an evolved form of SOA with more loose coupling. Each service operates
independently, making it highly scalable. In a microservices architecture, each service can also have its
own data storage, allowing it to operate without relying on other services.
Questions for Practice
Communication protocols define how data is exchanged between a client (like your web browser or app) and a
server. The goal is to manage requests and responses efficiently, especially under different conditions (high
demand, specific timing needs, etc.). Here are the main types:
1. Polling
● Explanation: Polling is like going to a shop and asking if a product is available. The client requests
information, and the server responds if it has the requested data. However, if the data isn’t ready, the
client just keeps asking until the server can fulfill the request.
● Limitations: Polling is simple but can strain the server if multiple clients make frequent requests. For
example, if 100 clients are constantly asking for the same new update, the server may struggle to
handle these repeated requests.
● Usage: Polling works well for situations where updates aren’t frequent but are still regular enough to
justify periodic checks.
2. Long Polling
● Explanation: Imagine you go to a shop, ask for a product, and the shopkeeper takes note of your
request. When the product is available, the shopkeeper will let you know instead of making you
repeatedly ask. Here, the client sends a request and the server keeps the connection open until it has a
response.
● Limitations: This approach increases server load because it has to keep requests open. The server
has to maintain a kind of "registry" for each request, adding complexity and resource usage.
● Usage: Long polling is used when you need updates that aren’t frequent but must be delivered as soon
as available, like news or notifications that should show up as soon as they’re posted.
3. Push
● Explanation: Push works like setting up a subscription. You tell the server you’re interested in updates,
and whenever there’s new data, it pushes that to you without any request from your side. Think of
notifications on your phone—like getting alerts about Instagram likes or new messages.
● Limitations: Push notifications can be a bit disruptive if data arrives when it’s not needed or if the client
isn’t actively using the service.
● Usage: Push is widely used in real-time notification systems, like email, chat apps, and social media
alerts.
4. Sockets (WebSockets)
● Explanation: Sockets allow continuous, real-time two-way communication. It’s like opening a phone
line—you can speak and listen simultaneously without needing to hang up and redial. The connection
remains open, and data can flow back and forth continuously.
● Usage: This is ideal for chat applications, live video streaming, and other systems that need rapid,
ongoing updates without the overhead of repeatedly opening and closing connections.
● Explanation: This is like push but happens only while the user is on a particular page. The client
subscribes to a data stream from the server, and the server continues sending updates until the client
disconnects. SSEs work well for updates that happen over long-lived connections, like live sports
scores or stock prices.
● Limitations: SSE only works as long as the page is open and active.
● Usage: Examples include live dashboards or continuously updating data pages.
In software architecture, a tiered architecture divides an application into multiple logical layers or "tiers" based
on functionality. Each tier is responsible for specific tasks, allowing for separation of concerns, scalability, and
maintainability. Here’s a look at the common tiers in a tiered architecture:
● Description: The presentation tier is the top-most layer responsible for the user interface. It interacts
directly with users and displays information from the system. It also collects input from users and sends
it to the application tier.
● Examples: Web browsers, mobile apps, desktop applications.
● Description: This tier contains the application’s business logic, processing rules, and core
functionalities. It processes inputs from the presentation tier, makes decisions, and interacts with the
data tier as necessary. This layer is crucial for implementing the application’s primary operations and
workflows.
● Examples: Web servers, APIs, business logic code.
● Description: The data tier is responsible for managing and storing data. It communicates with the
application tier to fetch, store, or update data. This layer often contains a relational or NoSQL database
and is optimized for data management.
● Examples: Databases like MySQL, PostgreSQL, MongoDB, or data warehouses.
1. 2-Tier Architecture
○ In a 2-tier architecture, the presentation and data layers communicate directly. This architecture
is typically simpler but has limited scalability and is less flexible.
○ Example: Client-server applications where a client directly interacts with the database.
2. 3-Tier Architecture
○ In a 3-tier architecture, the application logic is separated into its own tier, sitting between the
presentation and data layers. This separation allows for better scalability, security, and
maintenance.
○ Example: Most modern web applications use this structure, where the client (browser) talks to
the web server (logic tier), which then interacts with the database (data tier).
3. N-Tier Architecture
○ An extension of the 3-tier model, N-tier architecture includes additional layers, such as a service
layer or an integration layer. This setup is more flexible, allowing for complex, distributed
applications.
○ Example: Enterprise-level applications that may include multiple services, integrations, and
specialized business logic.
● Modularity: Allows for easier maintenance and updates, as each tier can be modified independently.
● Scalability: Each tier can be scaled independently to handle increased load.
● Security: Sensitive data can be protected in the data tier, with strict access control from the application
tier.
Let's break down authentication and authorization in simple terms, especially focusing on how you might
explain this to an interviewer in system design.
1. Understanding Authentication
● What it is: Authentication is the process of verifying the identity of a user. In simple words, it asks,
"Who are you?" When you try to log into a system, the system needs to confirm you are who you say
you are.
● Example: If your name is Ram and you want to access an application, you provide your
credentials—like a username and password. When you submit these, the system checks if they match
the stored records and confirms your identity. If the credentials are correct, it authenticates you. This
process is called authentication.
2. Understanding Authorization
● What it is: Authorization defines what actions you are allowed to perform after you are authenticated.
In other words, it asks, "What can you do?" Just because you have logged in does not mean you have
full control over everything.
● Example: Suppose you join a company, and after logging in, you access their database using your
credentials. You might be able to see the data but not necessarily modify it. Let’s say you have
“read-only” access; you can view tables but cannot delete or update records. The permissions you are
given—whether to read, update, or delete—are determined by authorization.
● Authentication: Confirms your identity. It’s like showing your ID to verify who you are.
● Authorization: Defines your permissions. It’s like showing a ticket at an event that only allows you into
certain areas.
Let’s delve into token-based authentication, a popular method used to enhance security and improve user
experience in applications. Here’s how you can explain it in an interview context, followed by questions to test
your understanding.
Token-Based Authentication Explained
1. Initial Registration:
○ The process starts with the client (user) registering on the application. During registration, the
user creates a username and password. Once registered, these credentials are stored securely
in the system.
2. Login Process:
○ When the user wants to log in, they provide their username and password. Upon successful
verification, the server generates a token (usually a JSON Web Token, or JWT). This token is
unique and contains information about the user's identity and possibly their permissions.
3. Using the Token:
○ After logging in, the user will receive this token. For every subsequent request to the server
(e.g., accessing resources or services), the user does not need to provide their username and
password again.
○ Instead, the user includes the token in the HTTP headers of their requests. This allows the
server to authenticate the user without needing the original credentials again, enhancing
security.
4. Token Expiration:
○ Tokens are typically designed to expire after a certain period (e.g., 30 minutes). This means that
if the user is inactive for too long, the token will become invalid. If the user tries to use an
expired token, they will need to log in again to obtain a new token.
○ For example, in a banking application, after logging in, the user receives a token. If they remain
inactive for a while, the token will expire, and they will be logged out for security reasons.
5. Advantages:
○ Security: Since passwords are not sent with every request, it minimizes the risk of interception.
○ Convenience: Users don't need to enter their credentials repeatedly.
○ Statelessness: Tokens allow the server to be stateless, meaning it doesn't need to keep track
of session states, which can improve scalability.
Understanding OAuth
OAuth (Open Authorization) is a widely used protocol that allows users to grant third-party applications limited
access to their resources on a different service without sharing their credentials (like passwords).
1. Security:
○ OAuth allows users to grant access to their information without exposing their passwords to
third-party applications. This minimizes the risk of credential theft.
2. Convenience:
○ Users can log into multiple applications without having to remember different usernames and
passwords for each service.
3. Granular Control:
○ Users can specify what data a third-party application can access and revoke permissions at any
time. For example, a user can allow an app to view their email but not modify it.
4. Standardization:
○ OAuth is an industry-standard protocol, which means many applications and services support it.
This makes it easier for developers to integrate secure authentication into their apps.
5. Single Sign-On (SSO):
○ OAuth is often used in conjunction with SSO solutions, allowing users to access multiple
applications with a single set of credentials.
A proxy server acts as an intermediary between a client (like a user's computer) and a server (like a web
service). It helps to route requests and responses between the client and the server. Let's break down the key
concepts and functions of a proxy server.
● A proxy server is either hardware or software that sits between a client and an application server,
providing various services, including anonymity, security, and caching.
1. Forward Proxy:
○ A forward proxy is used to access the internet on behalf of a client. When a client requests a
resource (like a webpage), the request goes to the forward proxy, which then forwards it to the
target server. The target server only sees the proxy's IP address, not the client's IP address.
○Example: If a student (client) wants to access a blocked website, they can use a forward proxy.
The proxy makes the request, and the blocked website does not know who the actual requester
is.
2. Reverse Proxy:
○ A reverse proxy sits in front of a web server and forwards requests to it. It can be used to
distribute load, provide SSL termination, and enhance security. Unlike a forward proxy, the client
is unaware of the reverse proxy's existence; they only know the IP of the reverse proxy.
1. Client Request:
○ When a client wants to access a resource (for example, a website), it sends a request to the
forward proxy.
2. Proxy Processes Request:
○ The proxy server receives the request and determines where to forward it based on the URL.
3. Request to Actual Server:
○ The proxy forwards the request to the actual server hosting the desired resource.
4. Response from Server:
○ The actual server sends the response back to the proxy.
5. Return to Client:
○ The proxy then sends the response back to the client.
6. Identity Concealment:
○ The actual server never sees the client's IP address; it only sees the proxy's IP address, which
helps to maintain the client's anonymity.
● Anonymity: The actual identity (IP address) of the client is hidden from the server, providing a level of
anonymity.
● Access Control: Organizations can use proxies to restrict access to certain websites or services based
on policies.
● Caching: Proxies can cache frequently accessed resources, reducing load times and saving
bandwidth.
● Bypassing Restrictions: Users can access blocked or restricted content by routing their requests
through a proxy server located in a different region.
● Improved Security: Proxies can act as an additional layer of security, filtering out malicious requests
before they reach the actual server.
A reverse proxy is a type of proxy server that sits in front of one or more web servers and forwards client
requests to them. It acts as an intermediary, but unlike a forward proxy that hides the client's identity, a reverse
proxy hides the identity of the backend servers from the clients. Let’s break down the concept:
1. Load Balancing:
○ Reverse proxies can distribute incoming traffic across multiple backend servers. This helps
manage load effectively and ensures no single server becomes overwhelmed, improving overall
performance and reliability.
2. Abstraction:
○ By using a reverse proxy, clients do not need to know the details of the backend servers. They
only interact with the reverse proxy, which abstracts the complexity of multiple servers.
3. Security:
○ A reverse proxy hides the identity and structure of the backend servers. If a client sends a
request, the actual server’s IP address remains concealed, protecting it from direct exposure to
the internet and potential attacks.
4. SSL Termination:
○ Reverse proxies can handle SSL encryption, allowing backend servers to focus on processing
requests rather than managing secure connections.
5. Caching:
○ They can cache responses from backend servers. If the same request is made multiple times,
the reverse proxy can serve the cached response instead of forwarding the request to the
server each time, thus improving response time and reducing load.
1. Client Request:
○ A client (like a web browser) makes a request for a resource (e.g., a webpage) using the
domain name (e.g., amazon.com).
2. Reverse Proxy Receives Request:
○ The request goes to the reverse proxy instead of directly to the backend servers.
3. Routing the Request:
○ The reverse proxy determines which backend server should handle the request based on the
URL or request type.
4. Forwarding to Backend Server:
○ The reverse proxy forwards the request to the appropriate backend server, which processes the
request.
5. Response from Backend Server:
○ The backend server sends the response back to the reverse proxy.
6. Return to Client:
○ The reverse proxy then sends the response back to the client, completing the request.
● Simplified Client Interaction: Clients interact with a single point (the reverse proxy), not worrying
about multiple backend servers.
● Enhanced Security: The backend server's IP addresses are not exposed, reducing the risk of attacks.
● Dynamic Routing: Requests can be routed to different servers based on various factors (like server
load or type of request), optimizing resource usage.
● Efficient Management: Administrators can change backend servers without disrupting service, as
clients continue to connect to the reverse proxy.