Data Structures & Algorithms: Industry Edition
Unit 3: Stacks, Queues & Recursion
Polish notation, expression evaluation, Tower of Hanoi, Merge Sort & Quick Sort ā with real examples from Swiggy, Paytm, and Amazon.
š¢ Real Projects | š» 4 Lab Programs (Python + C) | š 25 MCQs | šÆ 3 Interview Questions
Industry Hook ā The Real-World Problem First
š The Swiggy Problem: 50,000 Orders Per Hour, Zero Dropped
It's 8 PM on a Friday in Bangalore. Swiggy is processing 50,000+ orders per hour ā over 14 orders every second. Behind the scenes, two invisible data structures keep the entire system running:
- Order Queue (FIFO): Every new order enters the back of the queue. The kitchen sees orders in the exact sequence customers placed them. No order is skipped, no order cuts in line. This is a queue ā First In, First Out.
- Payment Transaction Stack (LIFO): When a customer pays ā¹500, Paytm's payment gateway records: "charge ā¹500" ā "verify OTP" ā "deduct from wallet." If the OTP fails, the system must undo operations in reverse order ā redo wallet credit, cancel verification, reverse charge. This is a stack ā Last In, First Out.
- Recursive Route Optimization: Swiggy's delivery algorithm breaks the city into zones, then sub-zones, then individual streets ā recursively dividing the problem until each piece is solvable. This is divide-and-conquer recursion ā the same principle behind Merge Sort and Quick Sort.
If Swiggy used an array instead of a queue, removing the first order would shift 50,000 elements ā the system would freeze. If Paytm used forward processing instead of a stack for rollbacks, failed transactions would corrupt account balances for millions of users.
This is exactly the problem stacks, queues, and recursion solve. Let's understand how.
Concept Explanation ā Theory, Earned
2.1 Stacks ā Last In, First Out (LIFO)
Layer 1 ā Intuition
A stack is a pile of plates in a hostel mess. You can only add a plate on top (push) and remove the plate on top (pop). You can't pull a plate from the middle without toppling the pile. The last plate placed is the first one taken ā LIFO.
Layer 2 ā Visual: Array vs Linked List Representation
Array-based Stack
top = 3
ā
āāāāāāā¬āāāāāā¬āāāāāā¬āāāāāā¬āāāāāā¬āāāāāā
ā 5 ā 12 ā 8 ā 3 ā ā ā capacity = 6
āāāāāāā“āāāāāā“āāāāāā“āāāāāā“āāāāāā“āāāāāā
[0] [1] [2] [3] [4] [5]
push(7): arr[4] = 7, top = 4 ā O(1)
pop(): return arr[3] = 3, top = 2 ā O(1)
Linked-List-based Stack
top
ā
āāāāāāā āāāāāāā āāāāāāā āāāāāāā
ā 3 āāāā¶ā 8 āāāā¶ā 12 āāāā¶ā 5 āāāā¶ NULL
āāāāāāā āāāāāāā āāāāāāā āāāāāāā
push(7): Create node [7], [7].next = top, top = [7] ā O(1)
pop(): return top.data, top = top.next ā O(1)
(Push/pop always at the HEAD ā that's why it's O(1))
Layer 3 ā Complexity Table
| Operation | Array Stack | LL Stack | Notes |
|---|---|---|---|
| Push | O(1)* | O(1) | *Amortized for dynamic array |
| Pop | O(1) | O(1) | Both just move the top pointer |
| Peek/Top | O(1) | O(1) | Read without removing |
| isEmpty | O(1) | O(1) | Check if top == -1 or top == NULL |
| Space | O(n) fixed | O(n) dynamic | LL uses extra pointer per node |
2.2 Arithmetic Expressions & Polish Notation
Why does this matter?
When you type 3 + 5 * 2 in a calculator, how does it know to multiply first? Humans use parentheses and BODMAS rules, but computers need a stack-based algorithm to parse and evaluate expressions. This is how every compiler, interpreter, and calculator app works.
Three Expression Formats
| Format | Example | Operator Position | Used By |
|---|---|---|---|
| Infix | A + B * C | Between operands | Humans |
| Prefix (Polish) | + A * B C | Before operands | Lisp, some calculators |
| Postfix (Reverse Polish) | A B C * + | After operands | Stack machines, HP calculators, Java bytecode |
Java's JVM and Python's bytecode compiler both convert your infix code to postfix internally. When you write x = a + b * c, the compiler generates: LOAD a, LOAD b, LOAD c, MULTIPLY, ADD, STORE x ā that's postfix! Every expression you've ever written gets converted using the stack algorithm below.
Infix ā Postfix Conversion (Shunting-Yard Algorithm)
Convert: A + B * C - D
Token Action Stack Output
āāāāā āāāāāā āāāāā āāāāāā
A Operand ā output (empty) A
+ Push (stack empty) + A
B Operand ā output + A B
* * > + precedence ā push + * A B
C Operand ā output + * A B C
- - ⤠* ā pop * to output + A B C *
- ⤠+ ā pop + to output (empty) A B C * +
push - - A B C * +
D Operand ā output - A B C * + D
END Pop remaining (empty) A B C * + D -
Result: A B C * + D - ā
Postfix Evaluation using Stack
Evaluate: 3 5 2 * + (which is 3 + 5 * 2 = 13)
Token Action Stack āāāāā āāāāāā āāāāā 3 Push [3] 5 Push [3, 5] 2 Push [3, 5, 2] * Pop 2,5 ā 5*2=10 [3, 10] + Pop 10,3 ā 3+10=13 [13] Result: 13 ā
2.3 Queues ā First In, First Out (FIFO)
Layer 1 ā Intuition
A queue is the line at a Swiggy delivery counter. The first order placed is the first one prepared and delivered. New orders join at the rear; completed orders leave from the front. No cutting in line!
Layer 2 ā Visual
Array-based Queue (Circular)
front=1 rear=4
ā ā
āāāāāāā¬āāāāāā¬āāāāāā¬āāāāāā¬āāāāāā¬āāāāāā
ā ā 20 ā 30 ā 40 ā 50 ā ā
āāāāāāā“āāāāāā“āāāāāā“āāāāāā“āāāāāā“āāāāāā
[0] [1] [2] [3] [4] [5]
Enqueue(60): rear = (4+1) % 6 = 5, arr[5] = 60
Dequeue(): return arr[1]=20, front = (1+1) % 6 = 2
Circular trick: rear = (rear + 1) % capacity
This reuses space when front advances, avoiding the "false full" problem.
Priority Queue & Deque
| Variant | Rule | Real Example |
|---|---|---|
| Queue (FIFO) | First in, first out | Swiggy order processing |
| Priority Queue | Highest priority dequeued first | Ola: nearest driver gets the ride |
| Deque (Double-ended) | Insert/delete at both ends | Browser history ā add at front, remove old from back |
2.4 Recursion: Divide, Conquer, Combine
Layer 1 ā Intuition
Recursion is like Russian nesting dolls (Matryoshka). Open the big doll ā inside is a smaller doll. Open that ā even smaller. Keep opening until you find the tiny solid doll (base case). Then you "close" them back up in reverse order (returning from recursive calls). Each doll is the same shape, just smaller ā that's the recursive structure.
The Three Laws of Recursion
- Base Case: A condition where the function stops calling itself (the tiny doll)
- Recursive Case: The function calls itself with a SMALLER problem
- Progress: Each call must move TOWARD the base case
Missing base case = infinite recursion = stack overflow. Every recursive call adds a frame to the call stack. Without a base case, the stack grows until memory runs out ā Python hits RecursionError at depth ~1000, C just crashes with a segfault. Always write your base case FIRST.
Merge Sort ā O(n log n) guaranteed
Visual
[38, 27, 43, 3, 9, 82, 10]
/ \
[38, 27, 43, 3] [9, 82, 10] ā DIVIDE
/ \ / \
[38, 27] [43, 3] [9, 82] [10] ā DIVIDE
/ \ / \ / \ |
[38] [27] [43] [3] [9] [82] [10] ā BASE CASE (size 1)
\ / \ / \ / |
[27, 38] [3, 43] [9, 82] [10] ā MERGE
\ / \ /
[3, 27, 38, 43] [9, 10, 82] ā MERGE
\ /
[3, 9, 10, 27, 38, 43, 82] ā MERGE (final)
Quick Sort ā average O(n log n), worst O(n²)
Visual
Pivot = last element. Partition: elements ⤠pivot go left, > pivot go right.
[10, 80, 30, 90, 40, 50, 70] pivot = 70
ā ā ā ā ā ā ā
[10, 30, 40, 50] [70] [80, 90] ā After partition
Then recursively sort left [10,30,40,50] and right [80,90].
| Algorithm | Best | Average | Worst | Space | Stable? |
|---|---|---|---|---|---|
| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | No |
Quick Sort has O(n²) worst case, yet it's used more often in practice than Merge Sort. Why? (Hint: Quick Sort is in-place with O(log n) space, while Merge Sort needs O(n) extra memory. For 1 billion elements, that's 4 GB of extra RAM. Also, Quick Sort has better cache locality.)