Important String Problems : Strings are everywhere in programming — from user input to text processing and coding interviews. Understanding common string problems will sharpen your problem-solving skills and prepare you for real-world coding challenges. Below are some fundamental string problems explained simply:
Important String Problems :
Reverse a String
Check whether a String is Palindrome or not
Find Duplicate characters in a string
Why strings are immutable in Java?
Write a Code to check whether one string is a rotation of another
Write a Program to check whether a string is a valid shuffle of two strings or not
Count and Say problem
Write a program to find the longest Palindrome in a string (Longest palindromic Substring)
Find Longest Recurring Subsequence in String
Print all Subsequences of a string
Print all the permutations of the given string
Split the Binary string into two substrings with equal 0’s and 1’s
Word Wrap Problem [VERY IMP]
EDIT Distance [Very Imp]
Find next greater number with same set of digits [Very Very IMP]
Key features of QNX : The QNX Neutrino microkernel architecture is designed for high reliability, modularity, and real-time performance, making it well-suited for embedded systems. Here are the key features of its architecture:
Key Features of QNX Neutrino Microkernel Architecture
Microkernel Design
Only the most essential services run in kernel space: CPU scheduling, IPC (Interprocess Communication), low-level memory management, and interrupt handling.
All other components (filesystems, drivers, network stacks, etc.) run in user space as separate processes.
Message Passing IPC
Processes communicate via a synchronous message-passing mechanism, which is fast and thread-safe.
This enables modularity, security, and fault isolation.
Fault Resilience and Isolation
If a user-space driver or service fails, it does not crash the entire system.
Services can be restarted dynamically without rebooting the system.
Deterministic Real-Time Performance
Designed for hard real-time systems with predictable latency.
Supports priority-based preemptive scheduling and priority inheritance.
Scalability and Modularity
Components can be included or excluded based on application needs.
Supports running on single-core, multi-core, and SMP systems efficiently.
Resource Manager Model
Filesystems, devices, and services are implemented as resource managers that respond to POSIX-style messages.
Allows seamless interaction with custom services as if they were regular files/devices.
POSIX Compliance
High degree of POSIX API support for portability and familiarity.
Supports multithreading with POSIX threads.
Security and Privilege Separation
Fine-grained permission model and the separation of services enhances system security.
Runs critical services with least privilege principle.
Support for Multi-Protocol and Multi-Architecture
Supports ARM, x86, PowerPC, MIPS, etc.
Provides protocol stacks for networking (TCP/IP, etc.), CAN, USB, and more.
Dynamic System Management
Services can be dynamically added, removed, or updated at runtime without reboot.
Useful in mission-critical systems like automotive, medical, and industrial applications.
Monolithic Kernel
Definition: A monolithic kernel is a single large process running entirely in a single address space (kernel space). All core services (e.g., device drivers, file systems, memory management, system calls) run inside the kernel.
Key Features:
All services run in kernel mode.
Fast performance due to direct communication.
Adding new features often requires recompiling the entire kernel.
A bug in one component (e.g., a driver) can crash the whole system.
Examples: Linux, Windows NT (older versions), UNIX
Microkernel
Definition: A microkernel keeps only the most essential functions (e.g., inter-process communication, basic scheduling, low-level address space management) in the kernel space. Other services (file systems, device drivers, etc.) run in user space as separate processes.
Key Features:
Emphasizes modularity and isolation.
Fault in a user-space service doesn’t crash the entire system.
Slower due to message passing overhead between services.
Easier to maintain and extend (e.g., add or update services without kernel recompilation).
Examples: QNX, MINIX, L4, seL4
Comparison Table
Feature
Monolithic Kernel
Microkernel
Structure
Single large program
Minimal kernel, services in user space
Performance
Fast
Slower due to IPC overhead
Stability
Less stable (one crash can affect all)
More stable (service crashes isolated)
Extensibility
Difficult to extend
Easier to extend/modify
Security
Less secure (shared memory)
More secure (isolated services)
Debugging
Harder
Easier (user-space services)
You can also Visit other tutorials of Embedded Prep
UART USART Interview Questions : Understand the difference between UART (Universal Asynchronous Receiver Transmitter) and USART (Universal Synchronous/Asynchronous Receiver Transmitter). You’ll learn that both are serial communication protocols, but USART can work in both asynchronous and synchronous modes, while UART works only in asynchronous mode.
Understand the Importance of Serial Communication: You will grasp why UART and USART are widely used for communication between microcontrollers and other devices like sensors, actuators, and peripherals, making it essential for embedded systems design.
2. Fundamental Components of UART and USART
Start, Data, and Stop Bits: You will learn about the data frame structure in UART/USART communication, which includes start bits, data bits, and stop bits. You’ll gain knowledge of how data is transmitted serially, one bit at a time.
Baud Rate, Parity, and Flow Control: Understand key communication parameters like baud rate, parity bit, and flow control mechanisms such as RTS/CTS (hardware flow control) and XON/XOFF (software flow control).
Baud Rate determines the speed of communication.
Parity provides a method to detect errors in data transmission.
Flow control prevents data loss by ensuring that the receiving end is ready to accept more data.
3. Data Transmission in UART/USART
Transmission Modes – Asynchronous vs. Synchronous: Learn the core difference between asynchronous communication (in UART) and synchronous communication (in USART) and when each mode is used.
In asynchronous mode, there is no clock signal. Data is sent using start and stop bits.
In synchronous mode, there is a shared clock signal, which allows for faster communication.
Learn About Full-Duplex and Half-Duplex: You’ll understand how full-duplex communication (where data can be transmitted and received simultaneously) works, as well as half-duplex (where data flows in only one direction at a time).
4. Configuring UART and USART Communication
Pin Configurations (TX/RX, RTS/CTS): Learn how to configure the essential pins of UART/USART like TX (Transmit) and RX (Receive) for basic communication. You’ll also learn about RTS and CTS pins used for hardware flow control.
Baud Rate Configuration: Learn how to configure the baud rate for accurate data transmission between two devices. You will understand how baud rates are calculated based on system clock settings.
Parity and Stop Bits: Learn how to set parity (even, odd, none) and configure the number of stop bits to ensure correct data transmission and error detection.
5. Error Handling in UART/USART
Framing Errors: Learn what framing errors are and how they occur when the receiver doesn’t receive the correct number of bits for each data frame.
Overrun Errors: Understand overrun errors that happen when the receiver’s buffer is full, and new data arrives before the previous data is processed.
Parity Errors: Learn how parity errors occur when the parity bit doesn’t match the expected value, signaling corrupted data.
6. Practical Applications of UART and USART
Communication Between Microcontrollers: Learn how to use UART/USART for communication between microcontrollers in a system.
Interfacing with Peripherals: Gain knowledge of how UART is commonly used to interface with peripherals like GPS modules, Bluetooth modules, and other devices for wireless communication, debugging, and more.
Debugging Embedded Systems via UART: Discover how serial debugging works, where UART is used to send debug messages from microcontrollers to a terminal for monitoring and troubleshooting.
7. Practical Considerations for UART/USART Implementation
Voltage Level Considerations: Learn about the voltage levels required for UART communication (e.g., 3.3V or 5V), and understand the importance of ensuring compatible voltage levels between devices or using level shifters.
Cable Length and Transmission Distance: Understand the limitations of UART communication, such as transmission distance, and the use of buffer amplifiers or signal conditioning for longer cable runs.
Setting Up a UART Communication Link: You will learn how to physically wire up UART links, configure microcontroller peripherals for UART operation, and troubleshoot issues like incorrect wiring, mismatched baud rates, or pin configurations.
8. Hands-On Learning
Writing Code to Initialize UART/USART: Learn how to write embedded code to initialize UART/USART on a microcontroller, configure parameters like baud rate, data bits, and flow control, and set up an interrupt-based UART communication system.
Sending and Receiving Data: Gain hands-on experience with sending and receiving data via UART using software libraries, handling the data in your code, and ensuring reliable communication.
Implementing Flow Control in UART Communication: You will learn how to implement hardware or software flow control to prevent data loss in high-speed communication scenarios.
9. Debugging and Troubleshooting UART/USART
Use of Serial Monitors and Logic Analyzers: Learn how to use tools like serial monitors to visualize data sent over UART. You’ll also discover how to use logic analyzers to troubleshoot UART/USART signals by capturing and decoding the data stream.
Testing Communication Between Devices: Learn how to test the UART communication link between two devices, identify common problems like baud rate mismatches, incorrect wiring, or noise in the data transmission.
What Skills You Will Gain:
UART/USART Protocols: A strong understanding of both asynchronous (UART) and synchronous (USART) communication, including how to configure them on embedded devices.
Error Handling: Practical knowledge of handling communication errors like framing, overrun, and parity errors.
Flow Control Management: How to implement flow control techniques (RTS/CTS and XON/XOFF) to ensure reliable communication in high-speed applications.
Embedded Programming Skills: Learn how to write embedded code for configuring, sending, and receiving data over UART/USART on microcontrollers and other devices.
Practical Troubleshooting: Master debugging techniques and use of tools like serial monitors and logic analyzers for troubleshooting UART/USART communication.
Learning Outcomes
By the end of your learning journey on UART and USART communication, you will:
Be able to configure UART/USART on embedded devices and interfaces.
Understand how data frames are structured and how transmission occurs bit by bit.
Be equipped with the skills to handle baud rate, flow control, and error management.
Gain hands-on experience in sending and receiving data in real-world embedded systems.
Learn to troubleshoot and debug UART communication using various tools and techniques.
Be proficient in implementing UART communication between multiple devices, including microcontrollers and peripherals.
What is UART/USART?
UART stands for Universal Asynchronous Receiver Transmitter.
USART stands for Universal Synchronous/Asynchronous Receiver Transmitter.
💡 Simple difference:
UART = Only Asynchronous communication.
USART = Can do both Synchronous and Asynchronous.
Basic Interview Questions
1. What is UART?
UART is a hardware communication protocol used to send and receive data serially (bit-by-bit) over two lines:
TX (Transmit)
RX (Receive)
It does not require a clock signal and is hence asynchronous.
2. What is the difference between UART and USART?
Feature
UART
USART
Clock
No clock
Can use clock (synchronous)
Mode
Asynchronous
Synchronous & Asynchronous
Speed
Slower
Faster in synchronous mode
3. What are the main pins in UART?
TX (Transmit) – sends data
RX (Receive) – receives data Optional:
GND – common ground between devices
4. How does UART communication work?
When UART sends data:
It adds a Start Bit (0)
Sends data bits (typically 8)
Adds an optional Parity Bit
Ends with one or more Stop Bits (1)
💡 Example Frame: [Start][D0][D1][D2]...[D7][Parity][Stop]
5. What is baud rate?
Baud rate is the speed of communication.
It defines the number of bits sent per second (bps).
Common values: 9600, 115200, etc.
Example: 9600 baud = 9600 bits per second.
6. Is UART full-duplex or half-duplex?
Full-duplex.
Can send and receive data simultaneously using separate TX and RX lines.
7. What is the difference between SPI, I2C, and UART?
Feature
UART
SPI
I2C
Wires
2 (TX, RX)
4 (MOSI, MISO, SCK, SS)
2 (SDA, SCL)
Speed
Medium
Fast
Slower
Master-Slave
Peer-to-peer
Master-Slave
Master-Slave
Complexity
Low
Medium
High
8. What is a parity bit in UART?
Used for error detection.
Even parity: number of 1s including the parity bit is even.
Odd parity: number of 1s including the parity bit is odd.
9. What happens if baud rates don’t match?
If two devices use different baud rates:
Data will be misaligned.
Receiver may see garbage data or framing errors.
10. How can you implement UART in embedded systems?
Two common ways:
Using built-in UART hardware module (e.g., STM32, Atmega)
Software UART (bit-banging) – manually control GPIOs using code (less reliable)
If you’re preparing for coding interviews or practicing data structure problems, chances are you’ll encounter the Next Greater Element in array problem. It’s one of the most common and important questions related to arrays and stacks — frequently asked by top tech companies.
In this beginner-friendly tutorial, we’ll explore the concept of Next Greater Element in an array with simple logic, step-by-step explanation, dry run examples, and multiple code implementations (brute-force and stack-based).
Whether you’re a student, a beginner programmer, or an aspiring software developer, this guide will help you build a strong foundation in solving array problems using stacks..
What is the Next Greater Element?
The Next Greater Element in array is a classic coding interview problem that tests your understanding of arrays and stack-based logic.
Here’s the problem statement:
Given an array of integers, your task is to find the next greater element for each element in the array. The next greater element for an element x is the first element to the right of x that is greater than x.
If no greater element exists on the right side, the answer is -1 for that position.
🔍 Example:
Input: [4, 5, 2, 25]
Output: [5, 25, 25, -1]
Explanation:
Next greater of 4 is 5
Next greater of 5 is 25
Next greater of 2 is 25
25 has no greater element, so answer is -1
This concept is commonly used in problems involving stock span, daily temperatures, and monotonic stacks. Understanding the Next Greater Element in array is a stepping stone to solving more advanced array-based questions.
Brute Force Approach to Find Next Greater Element in Array (Easy to Understand)
✅ Logic:
In this basic approach to solving the Next Greater Element in array, we use two nested loops. For each element in the array, we scan all elements to its right and find the first greater element.
If we don’t find any, we simply put -1.
🔁 Time Complexity:
O(n²) – Because for each element, we loop through the remaining elements on its right.
This method is easy to understand but not efficient for large input sizes.
🧑💻 C++ Code: Brute Force for Next Greater Element
#include <iostream>
#include <vector>
using namespace std;
void printNGE(vector<int>& arr) {
int n = arr.size();
for(int i = 0; i < n; i++) {
int next = -1;
for(int j = i + 1; j < n; j++) {
if(arr[j] > arr[i]) {
next = arr[j];
break;
}
}
cout << arr[i] << " --> " << next << endl;
}
}
int main() {
vector<int> arr = {4, 5, 2, 25};
printNGE(arr);
return 0;
}
Efficient Stack-Based Approach to Next Greater Element in Array (O(n) Time)
When solving the Next Greater Element in array, an efficient solution is to use a stack. This reduces the time complexity from O(n²) to O(n) and is widely asked in coding interviews.
🧠 Logic:
We process the array from right to left, using a stack to keep track of potential next greater candidates.
📚 Step-by-Step Dry Run:
For input array: [4, 5, 2, 25]
Start from the rightmost element.
For each element:
Remove all elements from the stack smaller than or equal to it.
If the stack becomes empty, the next greater element is -1.
Otherwise, the top of the stack is the next greater.
Push the current element onto the stack for future comparisons.
🧑💻 C++ Code: Stack-Based Next Greater Element
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
vector<int> nextGreaterElement(vector<int>& arr) {
int n = arr.size();
vector<int> result(n, -1);
stack<int> st;
for(int i = n - 1; i >= 0; i--) {
// Pop elements smaller than or equal to arr[i]
while(!st.empty() && st.top() <= arr[i]) {
st.pop();
}
// If stack is not empty, top is the next greater element
if(!st.empty()) {
result[i] = st.top();
}
// Push current element
st.push(arr[i]);
}
return result;
}
int main() {
vector<int> arr = {4, 5, 2, 25};
vector<int> res = nextGreaterElement(arr);
for(int i = 0; i < arr.size(); i++) {
cout << arr[i] << " --> " << res[i] << endl;
}
return 0;
}
🎯 Output:
4 --> 5
5 --> 25
2 --> 25
25 --> -1
Why Stack is Useful for Next Greater Element Problems?
Using a stack makes this problem highly efficient because it stores elements in a last-in, first-out (LIFO) structure, allowing us to track potential next greater elements without scanning the whole array repeatedly.
This stack-based method is especially powerful for large datasets and is frequently used in:
Stock span problems
Temperature prediction arrays
Monotonic stack patterns
Time & Space Complexity Summary
Approach
Time Complexity
Space Complexity
Brute Force
O(n²)
O(1)
Stack-based Method
O(n)
O(n)
More Problems Related to Next Greater Element in Array
Once you’re comfortable with the concept, try solving these related variations:
✅ Next Smaller Element in Array
✅ Next Greater Element in Circular Array
✅ Previous Greater Element in Array
✅ Daily Temperatures Problem (LeetCode)
✅ Stock Span Problem
Final Words
The Next Greater Element in array is a foundational algorithm that helps build problem-solving skills involving arrays, stacks, and greedy logic.
If you understand both the brute force and optimized approaches, you’ll be able to solve a wide range of real-world problems that follow a similar pattern.
Keep practicing and exploring new variations — and remember: every great coder once started with basics like this!
You can also Visit other tutorials of Embedded Prep
Welcome to the complete guide on mastering Stack Data Structure Whether you’re a beginner just learning data structures, preparing for interviews, or brushing up on recursion, this page will guide you through one of the most fundamental and powerful data structures.
In this tutorial series, you’ll not only learn how stacks work but also solve real-world problems using recursion, arrays, and linked lists.
What is a Stack?
A stack is a linear data structure that follows the Last In First Out (LIFO) principle — the last item added is the first one removed. Imagine a stack of plates: you add to the top and remove from the top. 🥞
In programming, stacks are used in:
Expression evaluation and syntax parsing
Undo operations in editors
Function call management (call stack)
Backtracking problems
Balanced parentheses checking
Depth-first search algorithms
Tutorials You’ll Find in This Series
Topic
What You’ll Learn
✅ Stack Implementation using Array
Learn how to create a basic stack using arrays. Understand operations like push, pop, peek, and check if it’s empty. Great for C++ and C beginners.
✅ 2 Stacks in One Array
Implement two independent stacks in the same array to save memory — a commonly asked space-optimization problem.
✅ Delete Middle Element of Stack
Practice recursion by deleting the middle element of a stack without using any extra data structure.
✅ Valid Parentheses Problem
Use stacks to validate expressions like {[()]}. This problem builds logic and appears in almost every coding test.
✅ Insert Element at Bottom of Stack
Dive into recursion by inserting an element at the bottom of the stack — an essential sub-step for reversing stacks.
✅ Reverse a Stack Using Recursion
Reverse the stack without any extra space using recursion — builds a strong grasp of recursive thinking.
✅ Sort a Stack Using Recursion
Sort stack elements in ascending order using only recursive calls. A true test of your logic-building skills.
✅ Redundant Brackets
Check if an expression has unnecessary or redundant brackets like "((a+b))". Frequently asked by top tech companies.
✅ Minimum Bracket Reversal
Given a string with only { and }, find the minimum reversals required to balance the expression. Learn how to count and pair brackets using a stack.
Why This Series is Perfect for Beginners
📌 Each topic starts from scratch with clear concepts.
💡 Includes dry runs, so you see how the stack changes step by step.
💻 Uses simple C++ code with detailed explanation.
🧪 Comes with practice problems to test your understanding.
📊 Some tutorials use diagrams to help visualize stack operations.
🚀 Recursion + Stack combo is perfect for strengthening logical thinking.
Build Your Foundations Strong
Mastering stacks is like building a strong base for competitive programming and interviews. Once you get confident with stacks, you’ll find it easier to solve:
Backtracking problems (e.g., Rat in a Maze, Sudoku Solver)
Expression Conversion (Infix to Postfix)
Tree traversals (postorder, inorder)
Recursion-based problems like Tower of Hanoi
Compiler syntax checks
Extra Practice Ideas
To make your learning journey even more interactive, try solving these after each tutorial:
🔄 Convert each recursive solution into an iterative one using a custom stack.
✍️ Write your own input/output examples and dry-run on paper.
💬 Try explaining the stack changes out loud or to a friend.
🎨 Draw the call stack if recursion is confusing — visuals help!
🧠 Combine multiple operations, like reversing and then sorting a stack.
Final Words
Stacks are the building blocks of logical problem solving. With each tutorial, you’re getting closer to mastering data structures and becoming an efficient coder.
So, what are you waiting for? 🚀 Dive into each topic listed above — whether you’re sorting, reversing, or validating brackets — and let’s master stacks together!
Happy Coding! 💻✨ Let’s stack up your skills — one pop and push at a time!
Sorting a Stack Using Recursion : In this tutorial, we will learn how to sort a stack of numbers using recursion in C++. A stack is a special data structure where you can only add or remove elements from the top, just like a stack of plates. Sorting a stack might seem tricky because you cannot access elements in the middle directly.
We will use recursion — a technique where a function calls itself — to break down the problem into smaller parts. First, we remove elements from the stack one by one until it’s empty. Then, we insert each removed element back into the stack in the correct sorted position.
This method does not use any extra data structures, and it helps you understand both recursion and stack operations deeply. The final result is a stack sorted in ascending order, with the smallest element at the bottom and the largest at the top.
The article also includes a step-by-step dry run to help beginners understand exactly how the stack changes during sorting.
What is a Stack?
A stack is a data structure that works like a pile of plates. You can only add or remove the top plate. This is called LIFO(Last In, First Out).
You push to add an item on the top.
You pop to remove the item from the top.
You can check the top element anytime.
What Does Sorting a Stack Using Recursion program Do?
The program sorts the elements in a stack so that the smallest number is at the bottom and the largest number is at the top — all using recursion (functions calling themselves).
Code Breakdown of Sorting a Stack Using Recursion
#include <iostream>
#include <stack>
using namespace std;
We include necessary libraries.
stack helps us use stack data structure.
iostream allows input/output.
Two Important Functions
sortedInsert — Inserts a number into the correct position in an already sorted stack.
sortStack — Sorts the entire stack using recursion.
Function: sortedInsert
void sortedInsert(stack<int> &st, int x) {
if (st.empty() || st.top() > x) {
st.push(x);
return;
}
int temp = st.top();
st.pop();
sortedInsert(st, x);
st.push(temp);
}
What it does: It inserts x into the stack st in sorted order.
How?
If stack is empty OR the top element is greater than x, push x directly (because it’s in the right position).
Otherwise, remove the top element temporarily, call sortedInsert recursively to insert x in the smaller stack.
After that, put back the temporarily removed element on top.
Function: sortStack of Sorting a Stack Using Recursion
void sortStack(stack<int> &st) {
if (st.empty()) return;
int x = st.top();
st.pop();
sortStack(st);
sortedInsert(st, x);
}
What it does: Sorts the stack by removing elements one by one and inserting them back in the sorted order.
How?
Remove the top element x.
Recursively sort the smaller stack.
Insert the removed element x back into the sorted stack using sortedInsert.
main Function — Driver Code of Sorting a Stack Using Recursion
Dry Run (Step by Step) | Sorting a Stack Using Recursion
Let’s dry run with the stack having elements (top to bottom):
40 (top), 20, 50, 10, 30 (bottom)
Step 1: sortStack is called with full stack
Pop 40
Recursively call sortStack with stack: 20, 50, 10, 30
Step 2: sortStack called again
Pop 20
Recursively call sortStack with stack: 50, 10, 30
Step 3: Next recursive call
Pop 50
Recursively call sortStack with stack: 10, 30
Step 4:
Pop 10
Recursively call sortStack with stack: 30
Step 5:
Pop 30
Recursively call sortStack with empty stack
Step 6:
Stack is empty, return from recursion
Step 7: Now insert back the elements in sorted order using sortedInsert
Insert 30 into empty stack → stack: 30
Step 8: Insert 10 into stack 30
Compare top 30 > 10 → push 10 on top → stack: 10, 30
Step 9: Insert 50 into stack 10, 30
30 < 50, pop 30
10 < 50, pop 10
Stack empty → push 50
Push back popped 10 → stack: 10, 50
Push back popped 30 → stack: 30, 10, 50 Oops! This looks reversed — wait, order is important. Let’s check carefully:
Actually, the function pushes back in this order:
After popping 30, then 10, stack empty
push 50 → stack: 50
push 10 → stack: 10, 50 (top is 10, then 50)
push 30 → stack: 30, 10, 50 (top is 30)
But this is wrong, we want the smallest on the bottom.
Actually, when printing, top is on the left.
Wait, the way we visualize is top at left (in code top is accessed by st.top()), so the stack is:
Top: 30
then: 10
then: 50
So 30 is on top, 10 below it, 50 at bottom → not sorted.
So something is wrong in our understanding.
But the code is correct. The explanation is: the stack stores elements with the top being the last pushed.
Let’s clarify: In the code, the stack top is the last inserted, and when we print from top to bottom, we pop top elements.
The sorted stack means top element is largest.
So the sorted stack (top to bottom): 50 40 30 20 10
Let’s do Step 9 carefully:
Stack before insertion: 10 (top), 30
Insert 50:
top is 10, which is not greater than 50 → pop 10
next top is 30, which is not greater than 50 → pop 30
stack empty → push 50
push back 30
push back 10
Stack now: Top → 10, then 30, then 50 (bottom)
Printing top to bottom: 10 30 50
So stack is not sorted this way.
So the stack sorted order after inserting elements means smallest at bottom, largest at top:
This means top is largest.
After all recursion and insertions, final stack will be:
50 (top), 40, 30, 20, 10 (bottom)
which is sorted in ascending order if you read bottom to top.
Summary of how recursion works here
We remove elements one by one until stack is empty.
Then we insert elements back in correct sorted order.
sortedInsert helps put element x in correct position by temporarily popping elements greater than x.
After insertion, the stack remains sorted from bottom (smallest) to top (largest).
Practice Tip:
To better understand how the stack changes during the sorting process, try adding cout (print) statements inside the functions. For example, print the current elements in the stack each time you push or pop something. This way, you can see step-by-step how elements move in and out of the stack. It helps you visualize the flow of recursion and makes it easier to follow the logic.
Watching the stack change in real time is a great way to learn and debug your code!
Practice Problems
Reverse a Stack Using Recursion Write a recursive function to reverse the elements of a stack without using any extra stack or array.
Insert an Element at the Bottom of a Stack Write a recursive function that inserts a given element at the bottom of a stack.
Sort a Stack Without Recursion Try to sort a stack using only stack operations (push, pop, top) but without recursion. You may use an additional stack or queue.
Find the Maximum Element in a Stack Using Recursion Write a recursive function that returns the maximum element present in a stack.
Delete the Middle Element of a Stack Using Recursion Given a stack, write a recursive function to delete the middle element of the stack without using any extra space.
You can also Visit other tutorials of Embedded Prep
Reverse a Stack Using Recursion : Stacks are an important data structure in programming. They work like a stack of plates — you can only add or remove plates from the top. This behavior is called LIFO (Last In, First Out).
Sometimes, you might want to reverse a stack, so the element that was at the top moves to the bottom and vice versa. But how do you reverse a stack without using extra memory or another stack? The answer is recursion!
In this article, we’ll learn how to reverse a stack using recursion by inserting elements at the bottom.
What is a Stack?
A stack allows only two main operations:
push(x): Add element x to the top.
pop(): Remove the top element.
You cannot directly access or modify elements below the top.
Problem Statement
Given a stack, reverse the order of elements using recursion. For example:
Original stack (top to bottom): 5, 4, 3, 2, 1
After reversing, the stack should be: 1, 2, 3, 4, 5
Key Idea: Insert at Bottom
Since we can only add or remove elements from the top, how do we insert an element at the bottom?
We use recursion:
If the stack is empty, push the element — that’s the bottom.
Otherwise, pop the top element, call the function recursively to insert the element at the bottom, then push the popped element back on top.
This way, the new element ends up at the bottom, and the original elements remain on top.
Step-by-Step Solution
1. Write a function to insert an element at the bottom:
void insertAtBottom(stack<int> &st, int x) {
if (st.empty()) {
st.push(x); // If empty, push element (bottom)
return;
}
int topElement = st.top();
st.pop(); // Remove top element
insertAtBottom(st, x); // Recursive call
st.push(topElement); // Push the popped element back on top
}
2. Write a function to reverse the stack:
void reverseStack(stack<int> &st) {
if (st.empty()) return; // Base case
int topElement = st.top();
st.pop(); // Remove top
reverseStack(st); // Reverse remaining stack
insertAtBottom(st, topElement); // Insert removed element at bottom
}
How Does This Work?
reverseStack() keeps popping the top element until the stack is empty.
Then it starts inserting each popped element at the bottom of the stack.
This reverses the order because the first popped element (which was originally on top) goes to the bottom.
Full Working Code
#include <iostream>
#include <stack>
using namespace std;
// Insert element x at the bottom of stack st
void insertAtBottom(stack<int> &st, int x) {
if (st.empty()) {
st.push(x);
return;
}
int topElement = st.top();
st.pop();
insertAtBottom(st, x);
st.push(topElement);
}
// Reverse the stack using recursion
void reverseStack(stack<int> &st) {
if (st.empty()) return;
int topElement = st.top();
st.pop();
reverseStack(st);
insertAtBottom(st, topElement);
}
// Print stack elements from top to bottom
void printStack(stack<int> st) {
while (!st.empty()) {
cout << st.top() << " ";
st.pop();
}
cout << endl;
}
int main() {
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
cout << "Original Stack: ";
printStack(st);
reverseStack(st);
cout << "Reversed Stack: ";
printStack(st);
return 0;
}
By cleverly using recursion, we can reverse a stack without using any extra data structure like another stack or array.
The trick lies in the helper function insertAtBottom() which helps us insert an element at the bottom of the stack, allowing us to place elements in reverse order during recursion unwinding.
If you want to practice further, try these exercises:
Write a function to reverse a stack without recursion (using an extra stack or queue).
Modify the code to reverse a stack of strings instead of integers.
Insert an Element at the Bottom of a Stack : Want to master stacks? This beginner-friendly tutorial walks you through how to insert an element at the bottom of a stack using recursion — without any extra data structures!
You’ll learn:
✅ What the problem means ✅ How recursion helps you simulate going deep into the stack ✅ Step-by-step explanation with dry run ✅ Full working code in C++ and Python ✅ Practice exercises to test your understanding
Perfect for beginners preparing for coding interviews or learning recursion concepts with stacks. Start building your foundation in data structures now!
Problem Statement Insert an Element at the Bottom of a Stack:
“Given a stack and an element, insert that element at the bottom of the stack.”
Step-by-step solution for Insert an Element at the Bottom of a Stack:
If the stack is empty, push the given element. ✅
Otherwise:
Pop the top element temporarily.
Recur to insert the element at the bottom.
Push the popped elements back after inserting. 🔄
This way, you dig down to the bottom using recursion, then insert, and rebuild the stack on your way back up.
C++ Code for Insert an Element at the Bottom of a Stack
#include <iostream>
#include <stack>
using namespace std;
// Function to insert element at the bottom of a stack
void insertAtBottom(stack<int>& st, int element) {
// Base case: stack is empty
if (st.empty()) {
st.push(element);
return;
}
// Step 1: Pop top element
int top = st.top();
st.pop();
// Step 2: Recursive call
insertAtBottom(st, element);
// Step 3: Push top back after inserting
st.push(top);
}
int main() {
stack<int> st;
// Push some elements
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
int newElement = 10;
insertAtBottom(st, newElement);
// Display stack
cout << "Stack after inserting at bottom:\n";
while (!st.empty()) {
cout << st.top() << " ";
st.pop();
}
return 0;
}
Python Code for Insert an Element at the Bottom of a Stack
def insert_at_bottom(stack, element):
# Base case: if stack is empty, insert element
if not stack:
stack.append(element)
return
# Step 1: Remove top
top = stack.pop()
# Step 2: Recursive call
insert_at_bottom(stack, element)
# Step 3: Put back the top
stack.append(top)
# Test
stack = [1, 2, 3, 4, 5]
element = 10
insert_at_bottom(stack, element)
print("Stack after inserting at bottom:")
print(stack[::-1]) # Print from top to bottom
Dry Run Example (Simple):
Let’s dry run the C++/Python code for inserting an element at the bottom of a stack, step by step.
Input:
Initial Stack (top -> bottom): [5, 4, 3, 2, 1]
Element to insert at bottom: 10
Function Call:
insertAtBottom(st, 10);
Dry Run – Step by Step (Recursion Unwinding)
Stack Before:
[top] → 5 → 4 → 3 → 2 → 1 → [bottom]
Step 1:
st.top() is 5 Pop 5 → Recur on smaller stack
Call: insertAtBottom([4, 3, 2, 1], 10)
Step 2:
Pop 4 → Recur again Call: insertAtBottom([3, 2, 1], 10)
Step 3:
Pop 3 → Recur again Call: insertAtBottom([2, 1], 10)
Step 4:
Pop 2 → Recur again Call: insertAtBottom([1], 10)
Step 5:
Pop 1 → Recur again Call: insertAtBottom([], 10)
Step 6 (Base Case):
Stack is empty → Push 10
✅ Stack: [10]
Backtracking Begins (Pushing elements back):
Now we return from previous function calls one-by-one:
🎉 Success! We inserted 10 at the bottom of the stack without using extra space
Why Use Recursion?
Stacks follow Last-In-First-Out (LIFO) — meaning you can only directly access or modify the top of the stack. There’s no built-in way to reach the bottom element directly.
But recursion gives us a trick:
It lets us temporarily pop all elements one by one.
Then, after reaching the empty stack (i.e., bottom), we insert the new element.
Finally, recursion rebuilds the original stack by pushing the saved elements back.
So, recursion simulates going deep into the stack, allowing us to insert at the bottom without breaking the stack’s rules!
Practice Exercises (to solidify your learning):
🔁 1. Reverse a stack using the insert_at_bottom() method. 💡 Tip: Use recursion to pop all elements, then reinsert using your insert-at-bottom function.
🔁 2. Insert an element at the bottom without using recursion, by using:
A second temporary stack, or
A queue (FIFO nature)
🔁 3. Implement stack reversal using both iterative and recursive approaches. 🔁 4. Write a function to find the size of a stack recursively. 🔁 5. Use insert_at_bottom() to sort a stack recursively in increasing order.
You can also Visit other tutorials of Embedded Prep
The Valid Parentheses Problem is a classic example of how stack data structures can be used to solve real-world problems involving nested or paired data. The challenge is to determine whether a given string containing only brackets—(, ), {, }, [, ]—is properly balanced and well-formed.
In a valid expression:
Every opening bracket must have a corresponding closing bracket.
Brackets must close in the correct order, maintaining the expected nesting.
To solve this, a stackis used to keep track of opening brackets. When a closing bracket appears, the algorithm checks whether it correctly matches the most recent unclosed opening bracket by looking at the top of the stack. If it doesn’t match or the stack is empty when it shouldn’t be, the expression is invalid. If the stack is empty after processing all characters, the expression is valid.
Valid Parentheses Problem stack description:
You are given a string that contains only the characters:
'(', ')', '{', '}', '[', ']'
Your task is to check whether the given string is a valid sequence of parentheses.
A valid string must follow these rules:
Every opening bracket must have a corresponding closing bracket.
The brackets must close in the correct order.
Each type of bracket must match correctly (() not [(]).
Why Use a Stack?
A stack is a data structure that works on Last In, First Out (LIFO). That means:
The last element you put in is the first one that comes out.
This is perfect for tracking open brackets and checking if they are closed properly.
Step-by-Step Logic for Valid Parentheses Problem stack:
Create an empty stack.
Loop through each character in the string.
If it is an opening bracket ((, {, [), push it onto the stack.
If it is a closing bracket:
Check if the stack is empty → if yes, return false.
Pop the top element from the stack.
Check if the popped element matches the current closing bracket → if not, return false.
After the loop, if the stack is empty, return true (valid). If not, return false (invalid).
Example of Valid Parentheses Problem stack:
Input:"{[()]}" Output:true Explanation: All brackets open and close in the correct order.
Input:"{[(])}" Output:false Explanation:[ is not properly closed before ) comes.
Key Concepts Covered:
Stack data structure
Matching pairs of brackets
Input validation
Control flow with conditionals
Problem Statement Valid Parentheses Problem stack:
Given a string containing just the characters '(', ')', '{', '}', '[', and ']', determine if the input string is valid.
👉 A string is valid if:
Open brackets are closed by the same type of brackets.
Open brackets are closed in the correct order.
Every closing bracket has a corresponding open bracket.
Check if the top of the stack is the matching opening bracket
In the end, the stack should be empty → ✅ Valid
C++ Code (Beginner-Friendly) | Valid Parentheses Problem stack
#include <iostream>
#include <stack>
#include <string>
using namespace std;
bool isValid(string s) {
stack<char> st;
for (char ch : s) {
// Push opening brackets
if (ch == '(' || ch == '{' || ch == '[') {
st.push(ch);
} else {
// If stack is empty when closing bracket comes → invalid
if (st.empty()) return false;
char top = st.top();
st.pop();
// Check matching pairs
if ((ch == ')' && top != '(') ||
(ch == '}' && top != '{') ||
(ch == ']' && top != '[')) {
return false;
}
}
}
// Stack should be empty if all brackets matched
return st.empty();
}
int main() {
string str = "({[]})";
if (isValid(str)) {
cout << "Valid" << endl;
} else {
cout << "Invalid" << endl;
}
return 0;
}
Dry Run Example: "({[]})"
Step
Char
Stack
Action
1
(
(
Push
2
{
({
Push
3
[
({[
Push
4
]
({
Pop + Match [
5
}
(
Pop + Match {
6
)
(empty)
Pop + Match (
→ Stack is empty ✅ → Valid
Python Code (Beginner-Friendly) | Valid Parentheses Problem stack
def isValid(s):
stack = []
mapping = {')': '(', '}': '{', ']': '['}
for char in s:
if char in mapping.values():
stack.append(char)
elif char in mapping:
if not stack or stack[-1] != mapping[char]:
return False
stack.pop()
else:
# In case of unexpected character
return False
return not stack
# Example usage
s = "{[()]}"
print("Valid" if isValid(s) else "Invalid")
Java Code (Beginner-Friendly) | Valid Parentheses Problem stack
import java.util.Stack;
public class ValidParentheses {
public static boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char ch : s.toCharArray()) {
if (ch == '(' || ch == '{' || ch == '[') {
stack.push(ch);
} else {
if (stack.isEmpty()) return false;
char top = stack.pop();
if ((ch == ')' && top != '(') ||
(ch == '}' && top != '{') ||
(ch == ']' && top != '[')) {
return false;
}
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
String s = "{[()]}";
System.out.println(isValid(s) ? "Valid" : "Invalid");
}
}
Beginner-Level Practice Problems
1. Basic Valid Parentheses
🔹 Problem: Check if the given string with only ()[]{} is valid.
🔹 Example:"([])" → true, "(]" → false
🔹 Goal: Implement the classic stack-based solution.
2. Minimum Add to Make Parentheses Valid
🔹 Problem: Given a string of parentheses '(' and ')', return the minimum number of additions needed to make it valid.
Structures in C : Unlock the power of structures in C programming with this beginner-friendly guide tailored for 2025 tech interviews! Whether you’re new to C or brushing up for embedded and systems programming interviews, this post offers a clear and practical explanation of structures — from basic syntax to advanced concepts like structure padding, bit-fields, and pointers to structures.
Dive deep into real-world examples, memory layout illustrations, and commonly asked interview questions that help you build a strong foundation and gain confidence in handling Structures data in C.
What You’ll Learn:
What is a structure and why it’s used in C
How to define, initialize, and access structure members
Structure padding and memory alignment
Bit-fields and their practical usage
Structure pointers and dynamic memory allocation
Array of structures and nested structures
Top interview questions and expert tips
Perfect for students, job-seekers, and professionals aiming to master data organization in C, this guide equips you with everything you need to ace interviews and write better, cleaner C code.
What is a Structure in C?
A structure is a user-defined data type that allows combining variables of different types under one name. Each variable inside a structure is called a member of the structure.
struct Date {
int day, month, year;
};
struct Employee {
int id;
struct Date joiningDate;
};
Array of Structures
struct Student students[5];
Why Use Structures?
To group logically related data
To create complex data models
To make code more readable and maintainable
Crucial for hardware register mapping in embedded C
Memory Layout of Structures
Structure members are stored sequentially in memory, and padding may be added for alignment. You can control this using #pragma pack (compiler-specific).
Important Notes:
You can’t initialize members inside the struct definition.
Structures can’t contain functions, but they can contain function pointers.
sizeof(struct) returns the size including padding bytes.
You can pass structure to functions by value or pointer.
What is the size of a structure in C if nothing is defined inside it?
In Standard C:
In standard C (C89/C90 and later), a structure must contain at least one member. An empty structure is not allowed and will typically give a compiler error.
This is done to ensure that even an empty structure occupies a unique memory location (not zero bytes), which is important for things like:
Arrays of structures
Memory alignment
Pointers
Summary Table:
Compiler
Empty struct allowed?
sizeof(struct)
GCC
✅ (extension)
1 byte
Clang
✅ (extension)
1 byte
MSVC
❌ (error)
Compilation error
Standard C
❌ (not allowed)
Compilation error
Interview Tip:
Q: What is the size of an empty structure in C?
A: In standard C, empty structures are not allowed and give a compile-time error. But in GCC (as an extension), it is allowed and the size is 1 byte.
Structure Padding in C
What is Padding?
Padding is the process of adding unused memory (bytes) between structure members to align data in memory according to the CPU architecture’s alignment requirements.
Why is Padding Needed?
Most processors access data faster when it’s aligned to its natural boundary:
int (4 bytes) should be stored at addresses divisible by 4
short (2 bytes) at addresses divisible by 2, etc.
To maintain this alignment, compilers insert padding bytes between structure members.
Example of Padding:
#include <stdio.h>
struct A {
char a; // 1 byte
int b; // 4 bytes
};
int main() {
printf("Size of struct A: %lu\n", sizeof(struct A));
return 0;
}
Output:
Size of struct A: 8
Explanation:
Member
Size
Offset
Notes
a
1 B
0
padding
3 B
1-3
for alignment
b
4 B
4-7
aligned on 4-byte boundary
How to Minimize Padding?
Reorder members from largest to smallest:
struct B {
int b; // 4 bytes
char a; // 1 byte
};
➡️ sizeof(struct B) will be 8, but more efficient than previous layout.
Use #pragma pack(1) to disable padding (GCC-specific):
But be careful—disabling padding can cause slower access or alignment faults on some hardware (especially embedded/ARM CPUs).
What is typedef in C?
typedef is a keyword in C (and C++) used to create a new alias (name) for an existing data type. It helps in making code more readable, portable, and easier to maintain.
Syntax:
typedef existing_type new_type_name;
Basic Example:
typedef unsigned int uint;
uint a = 10; // Same as: unsigned int a = 10;
Here, uint is a new name (alias) for unsigned int.
Why Use typedef?
Purpose
Benefit
Simplify complex types
Makes code readable and reduces clutter
Increase portability
Abstracts platform-dependent types (e.g., typedef int int32_t)
Easier maintenance
Update one typedef instead of multiple changes in code
Used with structs
Gives simple names to struct types without needing struct every time
typedef struct {
int id;
char name[50];
} Student;
With Pointers:
typedef int* IntPtr;
IntPtr p1, p2; // Both p1 and p2 are int pointers
Common Mistake:
typedef int* IntPtr;
IntPtr a, b;
Many think both a and b are int*, but they are. This is correct.
But if written as:
int* a, b; // a is int*, b is int (confusing)
This is why typedef helps avoid confusion.
Summary:
Feature
Description
Keyword
typedef
Purpose
Rename/alias a data type
Usage
Useful with structs, complex pointers
Benefit
Improves code clarity and maintainability
Bit Fields in Structures
What Are Bit Fields?
Bit fields allow you to allocate specific number of bits to structure members instead of full bytes.
Syntax:
struct Flags {
unsigned int a : 1;
unsigned int b : 2;
unsigned int c : 5;
};
a uses 1 bit
b uses 2 bits
c uses 5 bits
This is useful in:
Embedded systems (e.g., setting hardware registers)
Flag control
Memory optimization
Example:
#include <stdio.h>
struct Status {
unsigned int error : 1;
unsigned int ready : 1;
unsigned int mode : 2;
};
int main() {
struct Status s = {1, 0, 2};
printf("Size of Status: %lu\n", sizeof(s));
return 0;
}
Output:
Size of Status: 4
Even though total bits = 1 + 1 + 2 = 4 bits, size is still rounded to nearest word size (here: 4 bytes on 32-bit machine).
Rules and Notes:
Bit fields must be of integer type
You can’t take address of bit field
Compiler decides bit packing and alignment
Use unsigned int to avoid sign extension issues
Structure Pointers
What is a Structure Pointer?
A pointer to a structure stores the address of a structure variable. This is useful for:
Passing structures efficiently to functions
Dynamic memory allocation
Embedded register access
Example:
#include <stdio.h>
struct Student {
int id;
char name[20];
};
int main() {
struct Student s = {101, "John"};
struct Student *ptr = &s;
printf("ID: %d\n", ptr->id); // OR (*ptr).id
printf("Name: %s\n", ptr->name);
return 0;
}