Learn stack data structure in C with array-based and linked list implementations, push/pop operations, applications like expression evaluation, and interview questions.
A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. Think of it like a stack of plates – you can only add or remove from the top. Stacks are fundamental in computer science, used in function calls, expression evaluation, undo mechanisms, and backtracking algorithms.
Stack Operations
| Operation | Description | Time Complexity |
|---|
| Push | Add element to top | O(1) |
| Pop | Remove element from top | O(1) |
| Peek/Top | View top element without removing | O(1) |
| isEmpty | Check if stack is empty | O(1) |
| isFull | Check if stack is full (array only) | O(1) |
Stack Implementation Using Array
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int top;
} Stack;
// Initialize stack
void initStack(Stack *s) {
s->top = -1;
}
// Check if stack is full
bool isFull(Stack *s) {
return s->top == MAX_SIZE - 1;
}
// Check if stack is empty
bool isEmpty(Stack *s) {
return s->top == -1;
}
// Push element onto stack
void push(Stack *s, int value) {
if (isFull(s)) {
printf("Stack Overflow! Cannot push %d\n", value);
return;
}
s->items[++(s->top)] = value;
}
// Pop element from stack
int pop(Stack *s) {
if (isEmpty(s)) {
printf("Stack Underflow! Cannot pop\n");
return -1;
}
return s->items[(s->top)--];
}
// Peek at top element
int peek(Stack *s) {
if (isEmpty(s)) {
printf("Stack is empty\n");
return -1;
}
return s->items[s->top];
}
// Display stack
void display(Stack *s) {
if (isEmpty(s)) {
printf("Stack is empty\n");
return;
}
printf("Stack (top to bottom): ");
for (int i = s->top; i >= 0; i--)
printf("%d ", s->items[i]);
printf("\n");
}
int main() {
Stack s;
initStack(&s);
push(&s, 10);
push(&s, 20);
push(&s, 30);
push(&s, 40);
display(&s);
printf("Peek: %d\n", peek(&s));
printf("Pop: %d\n", pop(&s));
printf("Pop: %d\n", pop(&s));
display(&s);
printf("Size: %d\n", s.top + 1);
return 0;
}
Stack (top to bottom): 40 30 20 10
Peek: 40
Pop: 40
Pop: 30
Stack (top to bottom): 20 10
Size: 2
Stack Implementation Using Linked List
This implementation has no fixed size limit – it grows dynamically.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
typedef struct {
Node *top;
int size;
} Stack;
void initStack(Stack *s) {
s->top = NULL;
s->size = 0;
}
bool isEmpty(Stack *s) {
return s->top == NULL;
}
void push(Stack *s, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
if (!newNode) {
printf("Memory allocation failed\n");
return;
}
newNode->data = value;
newNode->next = s->top;
s->top = newNode;
s->size++;
}
int pop(Stack *s) {
if (isEmpty(s)) {
printf("Stack Underflow!\n");
return -1;
}
Node *temp = s->top;
int value = temp->data;
s->top = temp->next;
free(temp);
s->size--;
return value;
}
int peek(Stack *s) {
if (isEmpty(s)) return -1;
return s->top->data;
}
void display(Stack *s) {
Node *temp = s->top;
printf("Stack: ");
while (temp) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
void freeStack(Stack *s) {
while (!isEmpty(s)) pop(s);
}
int main() {
Stack s;
initStack(&s);
push(&s, 100);
push(&s, 200);
push(&s, 300);
display(&s);
printf("Size: %d\n", s.size);
printf("Pop: %d\n", pop(&s));
display(&s);
freeStack(&s);
return 0;
}
Stack: 300 200 100
Size: 3
Pop: 300
Stack: 200 100
Application: Balanced Parentheses Checker
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define MAX 100
typedef struct {
char items[MAX];
int top;
} CharStack;
void init(CharStack *s) { s->top = -1; }
bool empty(CharStack *s) { return s->top == -1; }
void push(CharStack *s, char c) { s->items[++(s->top)] = c; }
char pop(CharStack *s) { return empty(s) ? '\0' : s->items[(s->top)--]; }
bool isMatchingPair(char open, char close) {
return (open == '(' && close == ')') ||
(open == '{' && close == '}') ||
(open == '[' && close == ']');
}
bool isBalanced(const char *expr) {
CharStack s;
init(&s);
for (int i = 0; expr[i]; i++) {
if (expr[i] == '(' || expr[i] == '{' || expr[i] == '[') {
push(&s, expr[i]);
} else if (expr[i] == ')' || expr[i] == '}' || expr[i] == ']') {
if (empty(&s)) return false;
char top = pop(&s);
if (!isMatchingPair(top, expr[i])) return false;
}
}
return empty(&s);
}
int main() {
const char *tests[] = {
"{[()]}",
"((()))",
"{[(])}",
"((())",
"hello(world)[test]{done}"
};
for (int i = 0; i < 5; i++) {
printf("\"%s\" -> %s\n", tests[i],
isBalanced(tests[i]) ? "Balanced" : "Not Balanced");
}
return 0;
}
"{[()]}" -> Balanced
"((()))" -> Balanced
"{[(])}" -> Not Balanced
"((())" -> Not Balanced
"hello(world)[test]{done}" -> BalancedApplication: Infix to Postfix Conversion
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAX 100
char stack[MAX];
int top = -1;
void push(char c) { stack[++top] = c; }
char pop() { return stack[top--]; }
char peek() { return stack[top]; }
int empty() { return top == -1; }
int precedence(char op) {
switch (op) {
case '+': case '-': return 1;
case '*': case '/': return 2;
case '^': return 3;
default: return 0;
}
}
void infixToPostfix(const char *infix, char *postfix) {
int j = 0;
top = -1;
for (int i = 0; infix[i]; i++) {
char c = infix[i];
if (isalnum(c)) {
postfix[j++] = c;
} else if (c == '(') {
push(c);
} else if (c == ')') {
while (!empty() && peek() != '(')
postfix[j++] = pop();
if (!empty()) pop(); // Remove '('
} else { // Operator
while (!empty() && precedence(peek()) >= precedence(c))
postfix[j++] = pop();
push(c);
}
}
while (!empty())
postfix[j++] = pop();
postfix[j] = '\0';
}
int main() {
char postfix[MAX];
infixToPostfix("A+B*C", postfix);
printf("A+B*C -> %s\n", postfix);
infixToPostfix("(A+B)*C", postfix);
printf("(A+B)*C -> %s\n", postfix);
infixToPostfix("A+B*C-D/E", postfix);
printf("A+B*C-D/E -> %s\n", postfix);
return 0;
}
A+B*C -> ABC*+
(A+B)*C -> AB+C*
A+B*C-D/E -> ABC*+DE/-
Application: Postfix Expression Evaluation
#include <stdio.h>
#include <ctype.h>
int stack[100];
int top = -1;
void push(int val) { stack[++top] = val; }
int pop() { return stack[top--]; }
int evaluatePostfix(const char *expr) {
top = -1;
for (int i = 0; expr[i]; i++) {
if (isdigit(expr[i])) {
push(expr[i] - '0');
} else {
int b = pop();
int a = pop();
switch (expr[i]) {
case '+': push(a + b); break;
case '-': push(a - b); break;
case '*': push(a * b); break;
case '/': push(a / b); break;
}
}
}
return pop();
}
int main() {
printf("23*5+ = %d\n", evaluatePostfix("23*5+"));
printf("92-3*1+ = %d\n", evaluatePostfix("92-3*1+"));
printf("53+82-* = %d\n", evaluatePostfix("53+82-*"));
return 0;
}
23*5+ = 11
92-3*1+ = 22
53+82-* = 48
Comparison: Array vs Linked List Implementation
| Aspect | Array Stack | Linked List Stack |
|---|
| Size | Fixed (MAX_SIZE) | Dynamic (unlimited) |
| Memory | Pre-allocated | On-demand |
| Overflow | Possible | Only when memory exhausted |
| Cache performance | Better (contiguous) | Worse (scattered) |
| Extra memory | None | Pointer per node |
| Implementation | Simpler | Slightly complex |
Interview Questions on Stacks
Q1: How do you implement two stacks in one array? Start one stack from the beginning (grows right) and another from the end (grows left). They overflow only when the tops meet.
Q2: How to find the minimum element in O(1) time? Maintain an auxiliary stack that tracks the minimum. Push the new minimum alongside every push. The top of the aux stack always has the current minimum.
Q3: How do you sort a stack using only another stack? Pop from original, compare with top of temp stack. Pop elements from temp back to original until you find the right position, then push. Repeat until original is empty.
Q4: What's the difference between stack and recursion? Recursion uses the system call stack implicitly. Every recursive algorithm can be converted to an iterative one using an explicit stack.
Q5: How are stacks used in function calls? Each function call pushes a stack frame containing local variables, parameters, and return address. When the function returns, its frame is popped, restoring the caller's context.
Summary
Stacks are one of the most versatile data structures in computer science. The LIFO principle makes them perfect for managing function calls, evaluating expressions, parsing syntax, implementing undo functionality, and solving backtracking problems. Array-based stacks are simpler and cache-friendly but size-limited; linked list stacks offer dynamic sizing at the cost of memory overhead. Master both implementations and their applications for coding interviews and real-world projects.