What Are Positional Parameters in Shell Scripting?
Positional Parameters :Bash or shell scripting, positional parameters allow you to access arguments passed to a script or function. They are incredibly useful when you want to handle user inputs dynamically.
Why Learn Positional Parameters?
📦 Helps you write flexible scripts
🧑💻 Makes your scripts accept inputs
🔁 Supports automation and reusability
🧩 Essential for system scripting and DevOps
Key Positional Parameters Explained
Parameter
Meaning
$1, $2, …
First, second, etc., arguments
$0
Name of the script
$#
Total number of arguments
$@
All arguments as separate words
$*
All arguments as a single word
"$@"
Preserves spaces and treats arguments individually
Script Name: myscript.sh
First Argument: apple
Second Argument: banana
Total Arguments: 3
All Arguments (separately): apple banana cherry
All Arguments (together): apple banana cherry
Using "${@}" vs "${*}"
Let’s see the difference:
Example Script:
#!/bin/bash
echo "Using \"\$@\":"
for arg in "$@"; do
echo "$arg"
done
echo "Using \"\$*\":"
for arg in "$*"; do
echo "$arg"
done
Run with:
bash myscript.sh "hello world" "foo bar"
Output:
Using "$@":
hello world
foo bar
Using "$*":
hello
world
foo
bar
📌 "$@" keeps the full argument structure. "$*" splits everything.
Bonus: Accessing All Arguments with a Loop
#!/bin/bash
echo "You passed $# arguments."
i=1
for arg in "$@"; do
echo "Argument $i: $arg"
((i++))
done
Best Practices
Always quote your parameters: "$1", "$@"
Use $# to validate inputs
Use loops with "$@" for safe iteration
Real-world Use Case: Validate Arguments
#!/bin/bash
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <filename> <username>"
exit 1
fi
echo "Creating file $1 for user $2..."
5 Practical Examples Using Positional Parameters in Shell Scripts
Example 1: Greeting Script with $1 and $2
#!/bin/bash
echo "Hello, $1!"
echo "Welcome to $2."
Run:
bash greet.sh Nish India
Output:
Hello, Nish!
Welcome to India.
Example 2: Check If Arguments Are Passed Using $#
#!/bin/bash
if [ "$#" -lt 2 ]; then
echo "❌ Error: At least 2 arguments required."
echo "Usage: $0 <arg1> <arg2>"
exit 1
fi
echo "You passed $#: $1 and $2"
Run:
bash check.sh first
Output:
❌ Error: At least 2 arguments required.
Usage: check.sh <arg1> <arg2>
Example 3: Loop Through All Arguments Using $@
#!/bin/bash
echo "📦 List of arguments:"
for item in "$@"; do
echo "- $item"
done
Run:
bash list.sh Mango Banana Apple
Output:
📦 List of arguments:
- Mango
- Banana
- Apple
Example 4: Sum Two Numbers Using Positional Parameters
#!/bin/bash
num1=$1
num2=$2
sum=$((num1 + num2))
echo "The sum of $num1 and $num2 is: $sum"
Run:
bash sum.sh 5 15
Output:
The sum of 5 and 15 is: 20
Example 5: Handle All Arguments as One String Using $*
#!/bin/bash
echo "🧵 All arguments as one string: $*"
Run:
bash all.sh "one two" "three four"
Output:
All arguments as one string: one two three four
Summary Table
Symbol
What It Does
Example
$1
First argument
echo $1
$2
Second argument
echo $2
$#
Argument count
echo $#
$@
All arguments (safe loop)
for i in "$@"
$*
All arguments (single string)
echo $*
Conclusion
Mastering positional parameters in shell scripting is essential for writing interactive, powerful, and reusable scripts. Always remember:
$1, $2, etc. — access specific inputs
$# — count of arguments
"$@" — iterate safely
Use "$*" when combining arguments into a single string
Frequently Asked Questions (FAQ)
Q1: What are positional parameters in shell scripting?
Positional parameters are special variables in shell scripts that store command-line arguments passed to the script. For example, $1 is the first argument, $2 is the second, and so on.
Q2: What does $0 mean in a shell script?
$0 represents the name of the script itself. It’s often used in usage messages or logs to display which script is running.
Q3: What is the difference between $@ and $*?
$@ treats each argument as a separate string, ideal for loops.
$* treats all arguments as one single string, often used in quotes. Example:
for arg in "$@"; do echo "$arg"; done # Handles each argument correctly
for arg in "$*"; do echo "$arg"; done # May split arguments incorrectly
Q4: How do I check how many arguments were passed to the script?
Use $# to get the number of arguments.
echo "You passed $# arguments."
Q5: Can I access more than 9 arguments like $10, $11, etc.?
Yes, but use braces to avoid ambiguity.
echo "Tenth argument: ${10}"
Q6: How can I loop through all arguments in a script?
for arg in "$@"; do
echo "Argument: $arg"
done
This safely loops through all arguments with proper quoting.
Q7: What happens if an argument is missing in $1, $2, etc.?
It returns an empty string. Always validate input count using $# before accessing specific arguments.
Q8: Are positional parameters only available in Bash?
No, they’re available in all POSIX-compliant shells, including sh, Bash, Zsh, and Ksh.
Q9: How can I print all arguments including the script name?
echo "Script: $0"
echo "Arguments: $@"
Q10: Can I shift positional parameters inside a script?
Yes! Use shift to move parameters leftward:
shift
echo "Now \$1 is: $1"
You can also Visit other tutorials of Embedded Prep
CPP Design Patterns : Design patterns are proven solutions to common software design problems. They help developers write reusable, maintainable, and scalable code. Think of them as blueprints that guide how to structure your code in a way that solves recurring challenges.
Why Learn Design Patterns in C++?
C++ is widely used for system programming, game development, embedded systems, and real-time applications. Learning design patterns in C++ allows you to:
Write cleaner and more modular code
Improve collaboration in teams
Understand industry-level software design
Crack technical interviews easily
Types of Design Patterns
Design patterns are mainly divided into 3 categories:
Type
Description
Creational
Deal with object creation
Structural
Focus on object composition
Behavioral
Handle object interaction and communication
Examples of Each:
Creational: Singleton, Factory Method
Structural: Adapter, Decorator
Behavioral: Observer, Strategy
Real-World Analogy
Imagine you’re building a house:
Creational Pattern is like choosing how you build rooms (standard floor plan or customized).
Structural Pattern is like how rooms are connected (walls, doors).
Behavioral Pattern is like how people in rooms communicate (talking, signaling).
Tools You Need
To follow this C++ design pattern tutorial series, you need:
A basic understanding of C++ syntax
An IDE like Code::Blocks, VS Code, or CLion
A compiler like g++
C++ Design Patterns Table
Category
Pattern Name
Purpose
Creational
Singleton
Ensures a class has only one instance and provides a global access point.
Factory Method
Defines an interface for creating an object, but lets subclasses alter the type.
Abstract Factory
Provides an interface to create families of related objects.
Builder
Constructs a complex object step by step.
Prototype
Creates new objects by copying an existing one.
Structural
Adapter
Converts one interface into another expected by clients.
Bridge
Separates abstraction from implementation so they can vary independently.
Composite
Composes objects into tree structures to represent part-whole hierarchies.
Decorator
Dynamically adds behavior to objects.
Facade
Provides a unified interface to a set of interfaces in a subsystem.
Flyweight
Shares objects to support large numbers efficiently.
Proxy
Provides a surrogate or placeholder for another object.
Behavioral
Observer
Notifies all dependent objects of any changes to another object.
Strategy
Allows selecting an algorithm at runtime.
Command
Encapsulates a request as an object.
State
Allows an object to change its behavior when its internal state changes.
Template Method
Defines the program skeleton, letting subclasses fill in the steps.
Iterator
Provides a way to access elements of a collection sequentially.
Mediator
Defines an object that coordinates interaction between other objects.
Memento
Captures and restores an object’s internal state.
Visitor
Adds operations to classes without modifying them.
Interpreter
Interprets sentences in a language using a defined grammar.
Chain of Responsibility
Passes a request through a chain of handlers.
C++ Adding Two Numbers
#include <iostream>
class CalculatorSingleton {
private:
// Private constructor to prevent instantiation
CalculatorSingleton() {}
// Delete copy constructor and assignment operator
CalculatorSingleton(const CalculatorSingleton&) = delete;
CalculatorSingleton& operator=(const CalculatorSingleton&) = delete;
public:
// Public method to get the single instance
static CalculatorSingleton& getInstance() {
static CalculatorSingleton instance;
return instance;
}
// Method to add two numbers
int add(int a, int b) {
return a + b;
}
};
int main() {
// Get the singleton instance
CalculatorSingleton& calc = CalculatorSingleton::getInstance();
// Add two numbers
int result = calc.add(10, 20);
// Print the result
std::cout << "The sum is: " << result << std::endl;
return 0;
}
🔍 Output:
The sum is: 30
Frequently Asked Questions (FAQs)
What are design patterns in C++?
Design patterns in C++ are reusable solutions to common software design problems. They represent best practices used by experienced developers to solve recurring problems in software architecture and object-oriented design.
Why should I learn design patterns in C++?
Learning design patterns helps you:
Write clean, reusable, and maintainable code
Understand and apply industry-standard practices
Improve problem-solving and architecture design skills
Prepare for technical interviews and real-world development
Is this tutorial suitable for complete beginners?
Yes! This tutorial is specially designed for beginners. It explains design patterns in simple language, with easy-to-understand examples and real-world use cases in C++.
What topics will be covered in this C++ design patterns series?
The tutorial will cover:
Introduction to Design Patterns
Types of Design Patterns (Creational, Structural, Behavioral)
Step-by-step implementation of each pattern in C++
Best practices and use cases
Do I need to be an expert in C++ to follow this tutorial?
No. You just need to have a basic understanding of C++, such as classes, objects, inheritance, and functions. The tutorial is crafted for beginners and will guide you through each concept.
Are there code examples and explanations for each pattern?
Yes! Every design pattern in this series comes with:
Simple code examples in C++
Detailed explanations
Diagrams (where needed)
Real-world applications
Will this help in job interviews?
Absolutely. Many software engineering interviews include questions on design patterns. Understanding these concepts will give you a competitive edge during technical interviews.
How can I practice these design patterns?
Each tutorial includes:
Hands-on exercises
Code snippets you can run and modify
Mini-project ideas to build confidence
Is this Master CPP Design tutorials free?
Yes, the entire Cpp Design Patterns Tutorial for Beginners is free to read and follow. No sign-up or payment is required.
You can also Visit other tutorials of Embedded Prep
“Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.”
What It Really Means:
Imagine you’re working with a class called Bird that has a method fly(). If you create a subclass Penguin from Bird, but Penguin can’t fly, then substituting Bird with Penguin could break your program. That’s a violation of LSP.
The principle pushes us to design subclasses that truly behave like their parent class, so code that uses the base class doesn’t have to worry about the specific subclass being used.
LSP in Action (Simple Example):
class Bird {
public:
virtual void fly() {
std::cout << "Flying..." << std::endl;
}
};
class Sparrow : public Bird {
public:
void fly() override {
std::cout << "Sparrow flying!" << std::endl;
}
};
// This class violates LSP
class Ostrich : public Bird {
public:
void fly() override {
throw std::logic_error("Ostriches can't fly!");
}
};
In the example above, Ostrich breaks LSP because it can’t perform the fly() behavior expected from Bird.
Why It Matters:
Encourages safe inheritance.
Makes code more reliable and maintainable.
Helps avoid unexpected runtime errors.
Ensures that your class hierarchy makes logical sense.
How to Stick to LSP:
Don’t override methods in a way that changes their expected behavior.
Use composition over inheritance if subclass behavior doesn’t match the base class.
Clearly define contracts (what methods are supposed to do) and ensure subclasses respect them.
What is LSP?
Definition by Barbara Liskov:
If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program.
Simple Version:
A subclass should be able to stand in for its parent class without breaking the program.
What does that mean?
Let’s break it down:
You create a base class (Parent class).
Then you make a derived class (Child class).
The child should behave in a way that’s expected from the parent.
You should be able to use the child wherever you used the parent, and things should just work.
Real-Life Analogy
Let’s say:
You have a class “Bird” with a method fly().
You create a subclass “Parrot” — fine, it can fly.
Now you create a subclass “Penguin” — 🐧 uh-oh! Penguins can’t fly.
If you call fly() on a Penguin object assuming it’s a Bird, your program breaks the expectation.
Rectangle* r = new Square();
r->setWidth(5);
r->setHeight(10);
cout << r->getArea(); // Expected: 50, But it gives 100
Uh-oh! Because setting width or height sets both in Square. This breaks expected behavior. It violates LSP.
LSP-Compliant Refactor
Instead, don’t inherit Square from Rectangle directly if they behave differently.
Maybe use composition or separate hierarchy.
LSP Summary Table
Concept
Bad (Violates LSP)
Good (Follows LSP)
Penguin inherits Bird with fly()
Penguins can’t fly
Separate class for non-flying birds
Square inherits Rectangle
Area breaks expectations
Design Square separately
Subclass breaks parent’s rule
Throws, skips behavior
Behaves as parent promises
Key Tips to Follow LSP
Derived classes must honor contracts/behavior of base class.
Don’t override methods in a way that changes expected behavior.
Use interfaces or abstract classes to separate capabilities (like flying).
Prefer composition over inheritance when behaviors differ.
Want to Try a LSP-based Mini Project?
We can build:
A PaymentMethod base class (pay())
Subclasses: CreditCard, UPI, Wallet
And see how breaking or respecting LSP affects real logic
FAQ on Liskov Substitution Principle (LSP) in C++
1. What is the Liskov Substitution Principle (LSP) in C++?
The Liskov Substitution Principle (LSP) is one of the SOLID principles of object-oriented programming. It states that objects of a derived class should be replaceable with objects of their base class without affecting the correctness of the program. In simple terms, subclasses should behave like their parent classes without breaking functionality.
2. Why is the Liskov Substitution Principle important in software design?
LSP ensures code reliability, reusability, and maintainability. By following it, developers prevent unexpected behaviors when using inheritance. It reduces bugs, improves abstraction, and makes the system easier to extend in the future.
3. Can you give a simple C++ example of the Liskov Substitution Principle?
Yes.
#include <iostream>
using namespace std;
class Bird {
public:
virtual void fly() { cout << "Bird can fly\n"; }
};
class Sparrow : public Bird {
public:
void fly() override { cout << "Sparrow flying high\n"; }
};
// Substitution works correctly
int main() {
Bird* b = new Sparrow();
b->fly(); // Works fine as Sparrow behaves like Bird
delete b;
return 0;
}
Here, Sparrow can substitute Bird without breaking the program, which follows LSP.
4. What happens if LSP is violated in C++?
Violating LSP leads to runtime errors, unexpected behaviors, and poor code design. For example, if a derived class overrides a base class function in a way that changes its expected behavior, the system may produce wrong results or even crash.
5. How does the Liskov Substitution Principle relate to C++ design patterns?
Many C++ design patterns, such as Strategy, Template Method, and Factory Method, rely on LSP to ensure that subclasses can replace base classes seamlessly. Without LSP, these patterns lose their flexibility and correctness.
6. How can I check if my C++ code follows the Liskov Substitution Principle?
You can verify by asking:
Can I replace every instance of the base class with the subclass?
Does the subclass maintain the expected behavior of the parent? If the answer is yes, then your code follows LSP.
7. What are real-life examples of the Liskov Substitution Principle?
Vehicles: A Car is a type of Vehicle and can be used wherever a Vehicle is expected.
Shapes: A Circle should behave like a Shape without altering the expected behavior of a Shape class.
Payment Systems: A CreditCardPayment should work wherever a generic PaymentMethod is required.
8. How does LSP improve C++ code quality?
Promotes polymorphism
Reduces tight coupling
Makes code testable and scalable
Encourages clean architecture with fewer bugs
9. What is the difference between LSP and the Open-Closed Principle (OCP)?
LSP ensures that subclasses can replace base classes without breaking functionality.
OCP ensures that classes are open for extension but closed for modification. Both are connected — violating LSP often means violating OCP as well.
10. How is Liskov Substitution Principle used in modern C++ projects in 2025?
In 2025 C++ development, LSP is applied in:
Embedded systems for safe hardware abstraction
Game development for character and object hierarchies
Enterprise applications for scalable and maintainable architectures
Design patterns to ensure flexible and reusable solutions
You can also Visit other tutorials of Embedded Prep
When writing clean and maintainable code, the Single Responsibility Principle (SRP)is one of the most important design principles to follow. It is the first principle in the SOLID design principles, which are a set of five rules that help developers write better object-oriented code.
In this article, you’ll learn:
What the Single Responsibility Principle is
Why it matters
Real-world and code examples
Benefits of using SRP
Best practices to follow
What is the Single Responsibility Principle?
The Single Responsibility Principle (SRP) states:
“A class should have only one reason to change.“
This means that a class should do only one thing and should have only one responsibility. In other words, it should only focus on one task or job.
If a class is doing multiple unrelated tasks, then it violates SRP and becomes harder to understand, test, and maintain.
Real-World Analogy
Imagine you own a house and hire a cleaner, a plumber, and an electrician. Each one has a specific responsibility:
The cleaner cleans the house.
The plumber fixes water leaks.
The electrician repairs electric faults.
Now, imagine one person doing all three jobs. If anything goes wrong, it’s harder to pinpoint the issue or fix just one part without affecting the others.
The same concept applies in programming.
Code Example Without SRP (Bad Practice)
class Report {
public:
void generateReport() {
// Logic to generate the report
}
void printReport() {
// Logic to print the report
}
void saveToFile() {
// Logic to save the report to a file
}
};
What’s wrong here?
This class is doing too much:
Creating a report
Printing the report
Saving the report
These are different responsibilities, and each one might change for a different reason.
Code Example With SRP (Good Practice)
class ReportContent {
public:
void generateReport() {
// Only generates the report content
}
};
class ReportPrinter {
public:
void printReport() {
// Only responsible for printing
}
};
class ReportSaver {
public:
void saveToFile() {
// Only responsible for saving
}
};
Now, each class does only one job, and if anything changes, you only need to modify that specific class. This makes the code clean, testable, and easy to maintain.
Why is SRP Important?
Improves readability – Each class has a clear and focused purpose.
Simplifies testing – You can test each class independently.
Reduces bugs – Changes in one class don’t affect others unexpectedly.
Supports reusability – Classes with a single purpose are easier to reuse.
Enhances maintainability – Easier to fix issues or update code later.
How to Identify SRP Violations
Ask yourself:
Does the class do more than one thing?
Can I split this into smaller, more focused classes?
Are there multiple reasons this class might change?
If the answer is yes, then it’s time to refactor your code using SRP.
Best Practices to Follow
Focus each class on one and only one responsibility.
Write unit tests to check if a class is tightly focused.
Organize files and folders based on responsibilities.
Keep your methods short and clear.
Refactor often when a class starts growing too large.
Conclusion
The Single Responsibility Principle is all about keeping your code clean, organized, and focused. By giving each class a single job, you make your codebase easier to understand, test, and maintain — especially as your project grows.
You can also Visit other tutorials of Embedded Prep
Dependency Inversion Principle : “High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.”
Let’s Break It Down
This sounds complex, right? Let’s simplify.
Imagine you’re building a robot. The brain of the robot is the high-level module — it decides what to do. The legs or arms are low-level modules — they do the actual work.
Now, if the robot’s brain is tightly connected (depends directly) to the specific type of leg motors, then anytime you change the leg motors, you also have to change the brain’s code. That’s bad.
DIP says: “Don’t connect the brain directly to the legs. Let both talk through a common language (interface or abstract class).”
That way, if you ever replace the legs with wheels, the brain doesn’t need to know — it just sends the same instructions.
Increase flexibility, maintainability, and testability of code.
Make it easier to swap or change parts of the system.
Traditional (Wrong) Design Without DIP
class Keyboard {
public:
void input() {
std::cout << "Keyboard input received\n";
}
};
class Computer {
Keyboard keyboard; // Direct dependency
public:
void getInput() {
keyboard.input();
}
};
What’s wrong?
Computer is tightly bound to Keyboard.
If you want to replace Keyboard with Touchscreen, you have to change the Computer class.
DIP Applied (Right Way)
Step 1: Create an abstraction (interface)
class IInputDevice {
public:
virtual void input() = 0; // Pure virtual function
virtual ~IInputDevice() = default;
};
Step 2: Implement the abstraction in concrete classes
class Keyboard : public IInputDevice {
public:
void input() override {
std::cout << "Keyboard input received\n";
}
};
class Touchscreen : public IInputDevice {
public:
void input() override {
std::cout << "Touchscreen input received\n";
}
};
Step 3: Depend on the abstraction
class Computer {
IInputDevice* inputDevice; // Depends on abstraction
public:
Computer(IInputDevice* device) : inputDevice(device) {}
void getInput() {
inputDevice->input(); // Calls via interface
}
};
Step 4: Use it flexibly
int main() {
Keyboard kb;
Touchscreen ts;
Computer comp1(&kb); // Works with keyboard
Computer comp2(&ts); // Works with touchscreen
comp1.getInput(); // Output: Keyboard input received
comp2.getInput(); // Output: Touchscreen input received
return 0;
}
Key Takeaways
Concept
Explanation
High-Level Module
The main controller (e.g., Computer)
Low-Level Module
The working components (e.g., Keyboard, Touchscreen)
Abstraction
A shared interface both modules depend on (e.g., IInputDevice)
Dependency Inversion
The direction of dependency is flipped — both rely on abstraction
Benefits of Using DIP
Easy to switch or add new modules (like adding a mouse or joystick).
Improved testability (you can inject mock devices for testing).
Better code organization and readability.
Promotes Open/Closed Principle (open to extension, closed to modification).
Real-World Analogy
Think of a power plug. Your phone charger (low-level device) and your home socket (high-level supply) both depend on a standard plug shape (interface). If tomorrow you buy a new charger, as long as it supports the same plug, you don’t need to rewire your home.
DIP and Unit Testing
Since you depend on interfaces, you can write mock versions during testing:
class MockInputDevice : public IInputDevice {
public:
void input() override {
std::cout << "Mock input for testing\n";
}
};
Final Thoughts
DIP doesn’t mean no dependencies. It means depend on abstractions rather than concrete implementations.
It’s not about eliminating dependencies, but inverting the direction of dependency to favor abstraction.
Full Comparison of SOLID Principles
Principle
Full Form
Core Idea
High-Level Purpose
Real-World Analogy
Code Example Hint
Benefits
Violating It Leads To
S
Single Responsibility Principle
A class should have only one reason to change
Break down big classes into smaller ones, each doing one job
A chef shouldn’t also be the waiter, cashier, and cleaner
Split Invoice and InvoicePrinter
Maintainable, modular code
Hard to test, change, and reuse
O
Open/Closed Principle
Software entities should be open for extension, but closed for modification
Add new functionality without changing existing code
Adding new plugins to a browser without editing its core
Use inheritance or strategy pattern
Flexible and extendable code
Risk of breaking existing functionality
L
Liskov Substitution Principle
Subclasses should be replaceable for their parent classes without altering behavior
Design classes such that any subclass can be used safely in place of its base
A square should behave like a rectangle if inherited from it
Avoid incorrect inheritance like Bird -> Penguin flying
Polymorphic behavior works as expected
Unexpected behavior and bugs
I
Interface Segregation Principle
Clients shouldn’t be forced to depend on interfaces they don’t use
Break big interfaces into smaller, specific ones
Don’t give a remote with 50 buttons to someone who only wants to change the volume
Split IMultifunctionPrinter into IPrint, IScan
Minimal and focused contracts
Confusing, bloated interfaces
D
Dependency Inversion Principle
High-level modules should not depend on low-level modules, both should depend on abstractions
Use interfaces or abstract classes to decouple components
A plug point shouldn’t care which brand charger you use
Inject dependencies via constructor or interface
Flexible, testable, modular architecture
Tight coupling, hard to replace components
Detailed Descriptions Per Column
Core Idea
S: One job per class.
O: Add, don’t change.
L: Substitutable behavior.
I: Small, focused interfaces.
D: Depend on interfaces, not concrete classes.
Real-World Analogy
Each principle is inspired by common-sense organization and responsibility. These analogies help you remember and visualize them better.
Code Hint
Gives a small direction of what to implement or avoid in code for each principle.
Benefits
Each principle improves maintainability, testability, and flexibility in different ways. Together, they lead to a clean and scalable architecture.
Violations Lead To
Points out what goes wrong when the principle is not followed — like tight coupling, difficult changes, or confusing code.
How They Work Together (Flow Summary)
SRP gives each class one purpose.
OCP lets you grow your app by adding features instead of modifying old code.
LSP ensures that your new classes don’t break the old ones.
ISP keeps your classes from knowing too much they don’t care about.
DIP connects your parts flexibly through abstraction.
You can also Visit other tutorials of Embedded Prep
“Clients should not be forced to depend on interfaces they do not use.“
In simpler terms, this principle encourages splitting large, unwieldy interfaces into smaller, more specific ones so that classes only need to implement the methods they actually use.
Why Interface Segregation Principle Matters
When designing systems—especially in embedded software and large-scale applications—overly fat interfaces can lead to:
Unnecessary code coupling
Wasted resources in constrained systems
Fragile software that’s hard to maintain or extend
By adhering to ISP, you build cleaner, modular, and more maintainable codebases that are easier to test and evolve over time.
What does this mean (simple version)?
Imagine an interface (abstract class) with 10 methods. You only need 2 of them — but you still have to override all 10, even the ones you don’t use. 😖
That’s bad design.
With ISP, you split that big interface into smaller purpose-specific ones so that:
Classes only implement what they really need
They’re not bloated with useless methods
Real-Life Analogy
Let’s say you go to a restaurant.
If you only want coffee ☕, but they hand you a menu with 100 items, and ask you to order from every category — that’s overwhelming, right?
Wouldn’t it be better if they had a “Drinks” menu separate from the “Meals” and “Desserts”?
That’s Interface Segregation — keep things small, focused, and relevant.
Adapter Design Pattern : When working on large software systems, you may come across situations where two incompatible interfaces need to work together. Instead of modifying existing code (which may be risky or impossible), you can use the Adapter Pattern to make them compatible.
The Adapter Pattern acts like a bridge between two interfaces. It’s part of the Structural Design Patterns family in C++.
What is the Adapter Design Pattern?
The Adapter Design Pattern allows incompatible interfaces to work together by wrapping an existing class with a new interface.
In simple terms, it’s like using a power plug adapter that lets a U.S. plug fit into a European socket.
Real-life Analogy
Imagine you have an old VGA cable that only works with projectors, but your laptop only has an HDMI port. You can’t plug it in directly. So, you use an HDMI-to-VGA adapter. Now your laptop can talk to the projector!
That’s exactly what the Adapter Pattern does in code.
Components of the Adapter Pattern
Target – The expected interface (what your code is designed to use)
Adaptee – The existing class that needs adapting
Adapter – The class that wraps the Adaptee and makes it compatible with Target
When to Use the Adapter Pattern
When you want to reuse an existing class, but its interface doesn’t match what you need.
When you want to decouple code from third-party libraries or legacy code.
When integrating new features into old systems.
Adapter Pattern in C++ – Code Example
Let’s look at a simple C++ example to understand how it works.
Problem:
We have an existing OldPrinter class. But our system expects printers to use the NewPrinterInterface.
// Adapter
class PrinterAdapter : public NewPrinterInterface {
private:
OldPrinter* oldPrinter;
public:
PrinterAdapter(OldPrinter* printer) : oldPrinter(printer) {}
void printDocument(const std::string& text) override {
// Convert the request to match the old interface
oldPrinter->oldPrint(text);
}
};
Step 4: Client Code
int main() {
OldPrinter* legacyPrinter = new OldPrinter();
// Use adapter to make it compatible with the new interface
NewPrinterInterface* printer = new PrinterAdapter(legacyPrinter);
printer->printDocument("Hello, Adapter Pattern!");
delete printer;
delete legacyPrinter;
return 0;
}
Output
Old Printer Output: Hello, Adapter Pattern!
Success! The OldPrinter works with the new system using the adapter.
Types of Adapters
Class Adapter (using inheritance): Inherit from both Target and Adaptee (only works in languages with multiple inheritance like C++).
Object Adapter (using composition): Use a reference to Adaptee inside the Adapter. This is more flexible and used in our example above.
Benefits of Adapter Pattern
Promotes reusability of existing classes
Helps in code migration and integration
Makes your design more flexible and maintainable
Drawbacks
Adds extra layers of code
May lead to performance overhead
Can make debugging slightly harder due to indirection
Adapter vs. Other Patterns
Pattern
Purpose
Adapter
Makes incompatible interfaces compatible
Decorator
Adds behavior to an object dynamically
Bridge
Decouples abstraction from implementation
Facade
Provides a simplified interface
Conclusion
The Adapter Pattern is a powerful tool in a C++ developer’s design toolbox. It allows your code to stay clean, extensible, and compatible with legacy or third-party systems. By wrapping old interfaces with new ones, you avoid rewriting or duplicating code.
Use it wisely when integrating different systems — and you’ll be building robust and scalable software like a pro!
FAQs – Adapter Pattern in C++
Q1. Is Adapter Pattern a structural design pattern? Yes, it’s part of the Structural design patterns.
Q2. Can we implement Adapter without inheritance? Yes! You can use composition (object adapter) for a more flexible solution.
Q3. Is Adapter Pattern the same as a Wrapper? They are similar. A wrapper is a broader term; an adapter is a type of wrapper that converts interfaces.
You can also Visit other tutorials of Embedded Prep
Understand embedded startup code in depth from reset handlers to memory initialization with 5 critical insights engineers must know. Perfect for beginners and pros alike!
What is Startup Code? | Beginner-Friendly Guide
Startup code is a small but essential piece of code that runs before your main program starts. It prepares your system so that your program can run smoothly. This is especially important in embedded systems and low-level programming.
Simple Definition :
Startup code is the very first code that executes when a device is powered on or reset. It sets up the environment so that the main() function can run properly.
Why is Startup Code Important?
Think of startup code as a stage crew in a play. Before the actors come on stage, the crew sets the lighting, positions the props, and makes sure everything is ready. Similarly, startup code:
Sets up memory sections like .data, .bss, and stack
Initializes hardware components if needed
Calls constructors (in C++)
Finally, it jumps to the main() function
What Does Startup Code Typically Do?
Set the Stack Pointer Sets the stack to a known location in RAM.
Initialize Data Section Copies initialized global variables from Flash to RAM.
Zero out the BSS Section Sets all uninitialized global and static variables to zero.
Call System Initialization Code Optional hardware-specific setup.
Call main() Function After everything is set up, control is passed to your program’s main() function.
Where is Startup Code Found?
In embedded systems, startup code is usually written in assembly or C.
It is provided by the compiler toolchain (like GCC, Keil, etc.) or custom-written for a specific microcontroller.
Conclusion
Startup code may not be visible when writing high-level programs, but it plays a crucial role behind the scenes. Especially in embedded development, understanding how startup code works can help you debug issues, customize behavior, and build better systems.
What is a Clock : When working with embedded systems, one of the most important but often overlooked topics is the clock system. Just like our body has a heartbeat that keeps everything in sync, a microcontroller also relies on a clock to function properly.
In this post, we’ll break down the clock system in simple terms perfect for beginners looking to build a solid foundation in embedded development.
What is a Clock ?
In simple terms, a clock is a type of signal used to keep digital systems in sync. It’s like a heartbeat for electronic circuits, telling different parts when to start and stop tasks.
Technically, a clock is a square wave signal that switches between two logic levels:
High (H) – represents logic 1
Low (L) – represents logic 0
These transitions happen at regular intervals, creating a steady rhythm. This rhythm is crucial for coordinating operations in digital systems like microcontrollers, processors, and other integrated circuits.
To generate this clock signal, we use a component called an oscillator. Think of the oscillator as the “clockmaker” —> it’s the device responsible for producing the precise timing signals that drive the entire circuit.
What Does the MCU Do with the Clock Signal?
On your microcontroller board (MCU), there’s a small device called an oscillator. This oscillator sends out a steady stream of pulses—like ticking sounds of a clock. For example, it might be sending 20 million ticks per second (20 MHz).
Now the question is: What does the MCU actually do with those ticks?
1. The Clock Keeps the CPU Running
Inside the MCU, there’s a tiny computer brain called the CPU (Central Processing Unit). This CPU runs the program you’ve written—whether it’s blinking an LED or controlling a robot.
But the CPU doesn’t work randomly. It moves in steps, and each step is timed using the clock. For every tick (or pulse) from the oscillator, the CPU performs a small part of its job.
To run any program, the CPU repeats a 3-step cycle:
Fetch – Get the instruction from memory (like reading a recipe).
Decode – Understand what the instruction means (what kind of task it is).
Execute – Perform the task (do the actual work like math or moving data).
Every single line of code you write goes through these 3 steps, over and over again. And all of it happens in sync with the clock.
So, faster clock = faster program execution. That’s why high-performance MCUs can run at clock speeds like 200–300 MHz.
2. The Clock Also Powers the Peripherals
An MCU isn’t just a CPU—it also includes peripherals like timers, ADCs, UARTs, and more.
These peripherals also need the clock to function properly. For example:
A Timer uses the clock to count time and generate delays or periodic events (like a 1ms interrupt).
A Serial Port uses the clock to know when to send or receive data bits.
So the clock isn’t just the CPU’s heartbeat—it’s the lifeline of the whole system.
Boosting Clock Speed Using PLL
Here’s an interesting twist: Even though the oscillator provides only 20 MHz, many MCUs can run much faster.
How?
They use a special module called a PLL (Phase-Locked Loop). PLL takes the base clock (20 MHz) and multiplies it to reach higher speeds like 200 MHz.
So, the clock that finally runs the CPU is not always the same as what the oscillator gives. It’s first amplified using PLL, and then distributed to the CPU and peripherals.
In a Nutshell:
The clock tells the CPU when to run each part of your program.
It also powers the peripherals so they work properly.
Faster clocks = faster MCU performance.
The PLL boosts the clock to reach higher speeds.
You can think of the clock as the energy that drives everything inside the MCU.
Here’s a more human-friendly version:
Types of Clocks in a System:
System Clock: This is the heartbeat of your system, setting the pace for how fast the processor and all other parts of your device run.
Peripheral Clocks: These clocks are specialized for specific tasks. For example, they control things like timers, sensors, and communication ports (like UART or SPI) to keep everything running smoothly.
Real-Time Clock (RTC): This clock is used to keep track of the actual time, like a regular calendar. It’s low power, so it can keep running even when the rest of the system is powered down, which is handy for things like logging events or setting alarms.
Why is the Clock Important?
Every task inside a microcontroller — from blinking an LED to sending data over UART — depends on precise timing. Without a clock, nothing would run in sync.
Here’s why the clock is essential:
It defines the speed of the CPU.
It controls the timing of peripherals like timers, communication ports, and ADCs.
It affects power consumption and system performance.
Basic Components of a Clock System
Let’s look at the basic parts that make up a clock system:
1. Clock Source
This is where the clock signal originates. Common sources include:
Internal RC Oscillator – Built into the microcontroller, good for low-cost, less precise tasks.
External Crystal Oscillator – Very accurate, used when timing needs to be precise (e.g., real-time clocks or communication).
2. System Clock (SYSCLK)
This is the main clock used by the CPU and most of the system. It often comes from the selected clock source after processing (like through a PLL).
3. PLL (Phase-Locked Loop)
The PLL is used to multiply the base clock to get higher frequencies. For example, a 8 MHz crystal can be boosted to 72 MHz using a PLL.
4. Prescalers
Prescalers divide the clock frequency to provide slower clocks for specific modules like timers or ADCs.
5. Clock Tree
The clock tree distributes the clock to different parts of the microcontroller. Each branch can have different settings depending on the peripheral’s needs.
How to Choose the Right Clock?
Choosing the right clock source depends on:
Accuracy needed (crystal vs. internal).
Power consumption goals (lower frequency = less power).
Application requirements (e.g., high-speed data processing may need a faster clock).
For example:
A battery-powered sensor might use a low-frequency internal oscillator to save power.
A USB device needs a precise 48 MHz clock, typically achieved using a PLL with an external crystal.
Common Mistakes Beginners Make
Ignoring clock settings in code or configuration tools.
Choosing an unstable or inaccurate clock for communication protocols.
Not understanding how prescalers affect timing and delays.
Pro Tip for Beginners
When starting with a microcontroller, use the default clock settings first. Once you’re comfortable, experiment with different sources and frequencies to see how they impact the system.
Tools like STM32CubeMX, Atmel START, or ESP-IDF menuconfig can help configure clock systems without writing low-level code.
Follow-up interview questions based on your understanding of what is clocks ?
Clock & MCU Related Interview Questions
Basic Level
What is the purpose of a clock signal in a microcontroller?
What is the fetch-decode-execute cycle? Can you explain each step in simple terms?
Why does the CPU need to be synchronized with a clock signal?
What happens if the clock signal stops or becomes unstable?
How does increasing the clock frequency affect the performance of the MCU?
Intermediate Level
What is the role of an oscillator in a microcontroller system?
What is the difference between an internal and external clock source? When would you use each?
How does a PLL (Phase-Locked Loop) work and why is it used in MCUs?
What are the limitations of just increasing the clock speed in a microcontroller?
Explain how a peripheral like a Timer uses the clock to generate periodic interrupts.
Advanced Level
How would you configure the clock tree in an STM32 or similar MCU using CubeMX or directly in code?
Describe the impact of clock jitter on system performance, especially in ADCs or communication modules.
How do power consumption and clock speed relate in embedded systems?
Can you explain clock gating and how it helps in power optimization?
What’s the difference between synchronous and asynchronous clocks? When would asynchronous clocks be used inside an MCU?
Basic Level Answer :
What is the purpose of a clock signal in a microcontroller?
The purpose of a clock signal in a microcontroller is to provide a regular timing reference that synchronizes the operations of all internal components. In simple terms, the clock acts like a metronome, setting the pace for when the microcontroller fetches instructions, processes data, and communicates with peripherals.
More specifically:
It controls the speed at which the microcontroller executes instructions (called the clock frequency, usually measured in MHz).
It coordinates timing between the CPU, memory, and peripherals so they can work together properly.
It ensures deterministic behavior — meaning actions happen in predictable, timed cycles, which is very important in real-time systems.
Without a clock signal, the microcontroller wouldn’t know when to move from one operation to the next, and the system would not function correctly.
2.What is the fetch-decode-execute cycle? Can you explain each step in simple terms?
Ans : The fetch-decode-execute cycle is the basic process that a microcontroller (or any CPU) uses to run programs. You can think of it like a simple loop that happens over and over for each instruction.
Here’s a simple explanation of each step:
1. Fetch
The microcontroller reads (fetches) the next instruction from memory (usually from flash or RAM).
It uses a special pointer called the Program Counter (PC), which tells it where in memory the next instruction is.
Think of it like the microcontroller picking up the next task from a to-do list.
2. Decode
Once it has fetched the instruction, it figures out (decodes) what the instruction is asking it to do.
For example, is it an addition, a comparison, a memory load, or something else?
This is like the microcontroller reading the task and understanding what it needs to do.
3. Execute
The microcontroller carries out (executes) the instruction.
If the instruction says “add two numbers,” it adds them.
If it says “move data,” it moves it.
If it says “jump to a different part of the program,” it changes the Program Counter.
This is like the microcontroller actually doing the task, like calculating a result or turning on an LED.
Summary: Fetch → Get the instruction. Decode → Understand the instruction. Execute → Do the instruction.
Then it repeats for the next instruction… and that’s how your program runs!
3.Why does the CPU need to be synchronized with a clock signal?
The CPU needs to be synchronized with a clock signal because it ensures that all operations happen in a controlled, predictable, and orderly manner.
Here’s the simple idea:
A CPU is made of millions (or billions) of small circuits.
These circuits can’t all act randomly — they need to know exactly when to start and stop an operation.
The clock signal acts like a drumbeat, giving a steady rhythm.
On each clock pulse (rising or falling edge), the CPU knows it’s time to move to the next step: fetch an instruction, decode it, execute it, etc.
If there were no clock, or if parts of the CPU moved at different timings, it would cause:
Incorrect operations (data might not be ready yet).
Race conditions (two parts fighting over the same data).
System crashes (because sequences would happen out of order).
In short:
The clock keeps everything in sync, making sure the CPU works like a perfectly timed machine.
Real-world example: Imagine a group of dancers performing. If they don’t hear the same music beat, some will move early, some will move late — the whole performance will be a mess. The clock is like the music beat that keeps all dancers (circuits) in perfect harmony.
Would you also like me to explain how different clock speeds (like 8 MHz vs 100 MHz) affect CPU performance? ⏲️🚀
4.What happens if the clock signal stops or becomes unstable?
If the clock signal stops or becomes unstable, here’s what happens:
1. If the clock stops completely:
The CPU immediately freezes.
It can’t fetch, decode, or execute any more instructions.
The whole system halts — it’s like pressing pause on a video.
If it’s an embedded system (like a washing machine controller or a car ECU), the device will stop working instantly.
Simple way to think about it: If the drummer in a band stops beating the drum, the whole band will be confused and stop playing.
2. If the clock becomes unstable (irregular, noisy, glitchy):
Instructions might be fetched incorrectly or halfway.
Timing problems can cause:
Corrupted data.
Wrong instructions being executed.
Random system crashes (sometimes very hard to debug).
In embedded systems, this can lead to dangerous failures (like an airbag deploying at the wrong time or a motor running out of control).
Simple way to think about it: If the drummer starts beating randomly — sometimes too fast, sometimes too slow — the band members will get out of sync, play wrong notes, and the performance will collapse.
Summary:
Clock Condition
Result on CPU/System
Clock stops
System freezes
Clock unstable
System behaves unpredictably (crashes, errors, corruption)
In real devices:
Designers often use watchdog timers or clock monitors to reset the system automatically if the clock fails or becomes unstable.
5.How does increasing the clock frequency affect the performance of the MCU?
When you increase the clock frequency of a microcontroller (MCU):
➔ What happens?
The MCU can execute more instructions per second.
It fetches, decodes, and executes operations faster.
Overall performance improves — tasks are completed more quickly.
✅ Example: If an MCU runs at 8 MHz, it can (theoretically) process 8 million cycles per second. If you increase it to 16 MHz, it can process 16 million cycles per second — almost double the speed.
➔ But there are trade-offs:
Benefit
Drawback
Faster task execution
More power consumption
Quicker response time
More heat generation
Can handle more complex programs
May cause stability issues if clocking beyond design limits
➔ Simple analogy:
Clock frequency is like the heartbeat of the microcontroller. A faster heartbeat lets it work quicker, but tires it out faster (uses more energy and can overheat).
➔ Important notes:
In battery-powered devices (like sensors or wearables), lower frequencies are often used to save power.
Some MCUs allow dynamic frequency scaling — they increase speed when needed and lower it when idle to balance performance and power.
Summary: 🔵 Higher clock frequency = Faster MCU, but more power and heat. 🔵 Lower clock frequency = Slower MCU, but more energy-efficient.
You can also Visit other tutorials of Embedded Prep
Microcontroller vs Microprocessor vs SoC : Whether you’re a beginner stepping into embedded systems or an experienced engineer brushing up on core concepts, understanding the difference between a Microcontroller (MCU), Microprocessor (MPU), and System on Chip (SoC) is crucial. These are the brains behind every smart gadget—from your smartwatch to a satellite!
In this post, we’ll break it down simply with clear definitions, use cases, and a side-by-side comparison.
Microcontroller vs Microprocessor vs SoC
What is a Microcontroller (MCU)?
A Microcontroller is a compact integrated circuit designed to perform a specific control task in embedded systems.
Key Features:
Includes CPU + RAM + ROM + I/O peripherals on a single chip
Meant for real-time control
Low power, cost-effective
Runs simple firmware or embedded applications
Use Cases:
Washing machines, microwave ovens
Wearables like fitness trackers
Automotive dashboard systems
Arduino-based hobby projects
Note: Microcontrollers are ideal for small, low-power, task-specific systems.
What is a Microprocessor (MPU)?
A Microprocessor is a general-purpose CPU integrated into a single chip, but without built-in memory or peripherals.
Key Features:
Just the CPU
Needs external RAM, ROM, I/O
Designed for high performance and complex computing
Used in general-purpose computing
Use Cases:
Personal computers
Servers
Laptops
Desktop applications
Note : Microprocessors power general computing systems requiring external memory and peripherals.
What is a System on Chip (SoC)?
A System on Chip combines all components of a computer—CPU, GPU, RAM, storage, wireless modules, and I/O—on a single chip.
Key Features:
Combines MCU/MPU + peripherals + connectivity
Compact and power-efficient
Highly integrated
Custom-built for specific devices
Use Cases:
Smartphones and tablets
IoT devices
Smart TVs
Raspberry Pi
✅ Note : SoCs are the powerhouse of modern smart devices combining performance and efficiency.
Quick Comparison Table onMicrocontroller vs Microprocessor vs SoC
Feature
Microcontroller (MCU)
Microprocessor (MPU)
System on Chip (SoC)
Integration
CPU + RAM + ROM + I/O
CPU only
CPU + GPU + Memory + I/O
Application Type
Specific tasks (real-time)
General computing
Versatile, integrated systems
Cost & Power
Low
Higher
Medium to low
Performance
Moderate
High
High
Memory
Built-in
External
Built-in
Typical Use
Embedded devices
Computers, servers
Smartphones, IoT, tablets
Conclusion for Microcontroller vs Microprocessor vs SoC
Understanding the difference between a Microcontroller, Microprocessor, and SoC helps you pick the right solution for your embedded project or product. Here’s a quick rule of thumb:
🧩 Use MCU when you need low cost, low power, and a focused task
💻 Use MPU when you need high computation with external resources
📱 Use SoC when you want an all-in-one compact powerhouse
Follow-up interview questions Interviewer might askonMicrocontroller vs Microprocessor vs SoC
🔧 Microcontroller-Specific Questions:
Can you name a few popular microcontrollers you’ve worked with?
What are the advantages of using an MCU in real-time embedded systems?
How do you program a microcontroller, and which tools have you used?
What’s the difference between Harvard and Von Neumann architecture in the context of MCUs?
🖥️ Microprocessor-Focused Questions:
Why would you choose a microprocessor over a microcontroller in a design?
How do you handle memory management in systems using microprocessors?
What kind of operating systems typically run on microprocessor-based systems?
📱 SoC-Related Questions:
Can you give an example of a SoC you’ve worked with and what peripherals it included?
What challenges have you faced while developing software for SoCs?
How do SoCs support power optimization in battery-powered devices?
🤖 System Design and Application:
Suppose you have to design a smart home device. Will you choose an MCU, MPU, or SoC? Why?
Have you worked on bare-metal programming or RTOS on any MCU? Explain.
What are the criteria you consider while selecting between MCU, MPU, or SoC for an embedded product?
🧠 Conceptual or Deep-Dive Questions:
How does interrupt handling differ in MCUs vs MPUs?
Explain the boot process for an SoC-based system.
What’s the role of MMU in a microprocessor, and is it present in microcontrollers?
Can you compare an SoC running Android vs an MPU running embedded Linux?
You can also Visit other tutorials of Embedded Prep