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

Section 1

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.

šŸ‡®šŸ‡³ SwiggyšŸ‡®šŸ‡³ PaytmAmazonGoogle
Section 2

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

OperationArray StackLL StackNotes
PushO(1)*O(1)*Amortized for dynamic array
PopO(1)O(1)Both just move the top pointer
Peek/TopO(1)O(1)Read without removing
isEmptyO(1)O(1)Check if top == -1 or top == NULL
SpaceO(n) fixedO(n) dynamicLL 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

FormatExampleOperator PositionUsed By
InfixA + B * CBetween operandsHumans
Prefix (Polish)+ A * B CBefore operandsLisp, some calculators
Postfix (Reverse Polish)A B C * +After operandsStack 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

VariantRuleReal Example
Queue (FIFO)First in, first outSwiggy order processing
Priority QueueHighest priority dequeued firstOla: nearest driver gets the ride
Deque (Double-ended)Insert/delete at both endsBrowser history — add at front, remove old from back
Real consequence: Ola processes 2 million ride requests daily. Each request must find the nearest driver from 500,000 active drivers. A priority queue (min-heap) does this in O(log n) = ~19 comparisons. A linear search would need 500,000 comparisons — taking 26,000x longer per request.

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

  1. Base Case: A condition where the function stops calling itself (the tiny doll)
  2. Recursive Case: The function calls itself with a SMALLER problem
  3. 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].
AlgorithmBestAverageWorstSpaceStable?
Merge SortO(n log n)O(n log n)O(n log n)O(n)Yes
Quick SortO(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.)