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.)

Section 3

Lab Program Implementations

LAB PROGRAM 1: Stack โ€” Push and Pop (Array + Linked List)

๐Ÿ“˜ Syllabus: Unit 3 โ€” Stack Operations๐Ÿข Industry: Paytm transaction rollback, browser back button, undo in editors

Python โ€” Array-based Stack

Python
class ArrayStack:
    """Stack using a fixed-size array.
    Industry: Paytm's payment gateway uses a stack to undo
    transaction steps if OTP verification fails."""

    def __init__(self, capacity):
        self.stack = [None] * capacity
        self.top = -1          # -1 means empty
        self.capacity = capacity

    def is_empty(self):
        return self.top == -1

    def is_full(self):
        return self.top == self.capacity - 1

    def push(self, value):
        """Add element to top. O(1)."""
        if self.is_full():
            print("Stack Overflow!")
            return
        self.top += 1
        self.stack[self.top] = value

    def pop(self):
        """Remove and return top element. O(1)."""
        if self.is_empty():
            print("Stack Underflow!")
            return None
        value = self.stack[self.top]
        self.top -= 1
        return value

    def peek(self):
        if self.is_empty(): return None
        return self.stack[self.top]

    def display(self):
        print("Stack (topโ†’bottom):", self.stack[self.top::-1] if self.top >= 0 else "(empty)")


# === Driver ===
s = ArrayStack(5)
for val in [10, 20, 30]:
    s.push(val)
s.display()
print(f"Pop: {s.pop()}")
print(f"Peek: {s.peek()}")
s.display()
Stack (topโ†’bottom): [30, 20, 10] Pop: 30 Peek: 20 Stack (topโ†’bottom): [20, 10]

Python โ€” Linked-List-based Stack

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedStack:
    """Stack using linked list โ€” no size limit (dynamic)."""
    def __init__(self):
        self.top = None
        self.size = 0

    def push(self, data):
        new_node = Node(data)
        new_node.next = self.top   # New node points to old top
        self.top = new_node        # Update top
        self.size += 1

    def pop(self):
        if not self.top:
            print("Stack Underflow!"); return None
        data = self.top.data
        self.top = self.top.next   # Move top to next
        self.size -= 1
        return data

    def display(self):
        curr, parts = self.top, []
        while curr:
            parts.append(str(curr.data)); curr = curr.next
        print("Top โ†’ " + " โ†’ ".join(parts) + " โ†’ NULL")


ls = LinkedStack()
for v in [100, 200, 300]: ls.push(v)
ls.display()
print(f"Pop: {ls.pop()}")
ls.display()
Top โ†’ 300 โ†’ 200 โ†’ 100 โ†’ NULL Pop: 300 Top โ†’ 200 โ†’ 100 โ†’ NULL

C โ€” Array-based Stack

C
#include <stdio.h>
#define MAX 100

int stack[MAX], top = -1;

void push(int val) {
    if (top == MAX-1) { printf("Overflow!\n"); return; }
    stack[++top] = val;
}

int pop() {
    if (top == -1) { printf("Underflow!\n"); return -1; }
    return stack[top--];
}

int main() {
    push(10); push(20); push(30);
    printf("Pop: %d\n", pop());  // 30
    printf("Pop: %d\n", pop());  // 20
    return 0;
}

LAB PROGRAM 2: Queue โ€” Enqueue and Dequeue (Array + Linked List)

๐Ÿ“˜ Syllabus: Unit 3 โ€” Queue Operations๐Ÿข Industry: Swiggy order processing, printer spooler, CPU task scheduling

Python โ€” Circular Array Queue

Python
class CircularQueue:
    """Queue using circular array โ€” prevents the 'false full' problem.
    Industry: Swiggy's order pipeline uses this exact pattern."""

    def __init__(self, capacity):
        self.queue = [None] * capacity
        self.front = -1
        self.rear = -1
        self.capacity = capacity
        self.size = 0

    def is_empty(self): return self.size == 0
    def is_full(self): return self.size == self.capacity

    def enqueue(self, value):
        """Add element at rear. O(1)."""
        if self.is_full():
            print("Queue is full!"); return
        if self.is_empty():
            self.front = 0
        self.rear = (self.rear + 1) % self.capacity  # Circular wrap
        self.queue[self.rear] = value
        self.size += 1

    def dequeue(self):
        """Remove element from front. O(1)."""
        if self.is_empty():
            print("Queue is empty!"); return None
        value = self.queue[self.front]
        self.front = (self.front + 1) % self.capacity  # Circular wrap
        self.size -= 1
        if self.is_empty():  # Reset if empty
            self.front = self.rear = -1
        return value

    def display(self):
        if self.is_empty(): print("Queue: (empty)"); return
        parts = []
        for i in range(self.size):
            idx = (self.front + i) % self.capacity
            parts.append(str(self.queue[idx]))
        print("Front โ† " + " โ† ".join(parts) + " โ† Rear")


# === Driver: Swiggy order queue ===
orders = CircularQueue(5)
orders.enqueue("Order#101-Biryani")
orders.enqueue("Order#102-Pizza")
orders.enqueue("Order#103-Dosa")
orders.display()
print(f"Processing: {orders.dequeue()}")
orders.enqueue("Order#104-Momos")
orders.display()
Front โ† Order#101-Biryani โ† Order#102-Pizza โ† Order#103-Dosa โ† Rear Processing: Order#101-Biryani Front โ† Order#102-Pizza โ† Order#103-Dosa โ† Order#104-Momos โ† Rear

C โ€” Array Queue + Linked List Queue

C
#include <stdio.h>
#include <stdlib.h>
#define MAX 100

/* Array-based circular queue */
int q[MAX], front = -1, rear = -1, qsize = 0;

void enqueue(int val) {
    if (qsize == MAX) { printf("Full!\n"); return; }
    if (front == -1) front = 0;
    rear = (rear + 1) % MAX;
    q[rear] = val; qsize++;
}

int dequeue() {
    if (qsize == 0) { printf("Empty!\n"); return -1; }
    int val = q[front];
    front = (front + 1) % MAX;
    qsize--;
    return val;
}

/* Linked-list queue */
struct QNode { int data; struct QNode* next; };
struct QNode *qfront = NULL, *qrear = NULL;

void llEnqueue(int val) {
    struct QNode* n = (struct QNode*)malloc(sizeof(struct QNode));
    n->data = val; n->next = NULL;
    if(!qrear) { qfront = qrear = n; return; }
    qrear->next = n; qrear = n;
}

int llDequeue() {
    if(!qfront) { printf("Empty!\n"); return -1; }
    int val = qfront->data;
    struct QNode* t = qfront;
    qfront = qfront->next;
    if(!qfront) qrear = NULL;
    free(t);
    return val;
}

int main() {
    enqueue(10); enqueue(20); enqueue(30);
    printf("Dequeue: %d\n", dequeue());  // 10
    llEnqueue(100); llEnqueue(200);
    printf("LL Dequeue: %d\n", llDequeue());  // 100
    return 0;
}

LAB PROGRAM 3: Tower of Hanoi using Recursion

๐Ÿ“˜ Syllabus: Unit 3 โ€” Recursion๐Ÿข Industry: Backup systems (incremental migration), divide-and-conquer

Python Implementation

Python
def tower_of_hanoi(n, source, auxiliary, destination):
    """Move n disks from source to destination using auxiliary.
    
    The key insight:
    1. Move n-1 disks from source to auxiliary (recursive)
    2. Move the largest disk from source to destination (base move)
    3. Move n-1 disks from auxiliary to destination (recursive)
    
    Total moves = 2^n - 1. For n=64 (legend): 18 quintillion moves!
    """
    if n == 1:
        # Base case: only one disk โ€” move it directly
        print(f"Move disk 1 from {source} to {destination}")
        return
    
    # Step 1: Move top n-1 disks out of the way
    tower_of_hanoi(n - 1, source, destination, auxiliary)
    
    # Step 2: Move the largest disk
    print(f"Move disk {n} from {source} to {destination}")
    
    # Step 3: Move the n-1 disks from auxiliary to destination
    tower_of_hanoi(n - 1, auxiliary, source, destination)


print("=== Tower of Hanoi (3 disks) ===")
tower_of_hanoi(3, "A", "B", "C")
print(f"\nTotal moves for 3 disks: {2**3 - 1} = 7")
=== Tower of Hanoi (3 disks) === Move disk 1 from A to C Move disk 2 from A to B Move disk 1 from C to B Move disk 3 from A to C Move disk 1 from B to A Move disk 2 from B to C Move disk 1 from A to C Total moves for 3 disks: 7

C Implementation

C
#include <stdio.h>

void hanoi(int n, char src, char aux, char dst) {
    if (n == 1) {
        printf("Move disk 1: %c -> %c\n", src, dst);
        return;
    }
    hanoi(n-1, src, dst, aux);
    printf("Move disk %d: %c -> %c\n", n, src, dst);
    hanoi(n-1, aux, src, dst);
}

int main() {
    hanoi(3, 'A', 'B', 'C');
    return 0;
}

The Tower of Hanoi requires 2โฟ - 1 moves. For the legendary 64-disk version, that's 18,446,744,073,709,551,615 moves. At 1 move per second, it would take 585 billion years โ€” about 42 times the age of the universe. This is the power (and danger) of exponential growth.

LAB PROGRAM 4: Merge Sort and Quick Sort (Recursive)

๐Ÿ“˜ Syllabus: Unit 3 โ€” Recursive Sorting๐Ÿข Industry: Python sorted() uses Timsort (merge sort variant). C stdlib qsort() uses Quick Sort.

Merge Sort โ€” Python

Python
def merge_sort(arr):
    """Divide array in half, sort each half, merge sorted halves.
    Time: O(n log n) always. Space: O(n). Stable.
    Used by: Python's Timsort, Java's Arrays.sort() for objects."""
    
    if len(arr) <= 1:    # Base case: single element is sorted
        return arr
    
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])     # Sort left half
    right = merge_sort(arr[mid:])    # Sort right half
    
    return merge(left, right)


def merge(left, right):
    """Merge two sorted arrays into one sorted array. O(n)."""
    result = []
    i = j = 0
    
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:    # <= for stability
            result.append(left[i]); i += 1
        else:
            result.append(right[j]); j += 1
    
    result.extend(left[i:])      # Append remaining
    result.extend(right[j:])
    return result


data = [38, 27, 43, 3, 9, 82, 10]
print("Merge Sort:", merge_sort(data))
Merge Sort: [3, 9, 10, 27, 38, 43, 82]

Quick Sort โ€” Python

Python
def quick_sort(arr, low, high):
    """In-place Quick Sort using Lomuto partition.
    Average: O(n log n). Worst: O(nยฒ) on already-sorted input.
    Used by: C's qsort(), Go's sort, Rust's sort_unstable."""
    
    if low < high:
        pivot_index = partition(arr, low, high)
        quick_sort(arr, low, pivot_index - 1)   # Sort left
        quick_sort(arr, pivot_index + 1, high)  # Sort right


def partition(arr, low, high):
    """Lomuto partition: last element as pivot."""
    pivot = arr[high]          # Choose last element as pivot
    i = low - 1                # Boundary of "โ‰ค pivot" section
    
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]   # Swap to left side
    
    arr[i + 1], arr[high] = arr[high], arr[i + 1]  # Place pivot correctly
    return i + 1


data = [10, 80, 30, 90, 40, 50, 70]
quick_sort(data, 0, len(data) - 1)
print("Quick Sort:", data)
Quick Sort: [10, 30, 40, 50, 70, 80, 90]

C โ€” Merge Sort & Quick Sort

C
#include <stdio.h>

/* โ”€โ”€โ”€ Merge Sort โ”€โ”€โ”€ */
void merge(int a[], int l, int m, int r) {
    int n1=m-l+1, n2=r-m, L[n1], R[n2];
    for(int i=0;i<n1;i++) L[i]=a[l+i];
    for(int j=0;j<n2;j++) R[j]=a[m+1+j];
    int i=0,j=0,k=l;
    while(i<n1 && j<n2) a[k++]=(L[i]<=R[j])?L[i++]:R[j++];
    while(i<n1) a[k++]=L[i++];
    while(j<n2) a[k++]=R[j++];
}
void mergeSort(int a[], int l, int r) {
    if(l<r) { int m=l+(r-l)/2; mergeSort(a,l,m); mergeSort(a,m+1,r); merge(a,l,m,r); }
}

/* โ”€โ”€โ”€ Quick Sort โ”€โ”€โ”€ */
int partition(int a[], int lo, int hi) {
    int pivot=a[hi], i=lo-1;
    for(int j=lo;j<hi;j++)
        if(a[j]<=pivot){i++; int t=a[i];a[i]=a[j];a[j]=t;}
    int t=a[i+1]; a[i+1]=a[hi]; a[hi]=t;
    return i+1;
}
void quickSort(int a[], int lo, int hi) {
    if(lo<hi){int p=partition(a,lo,hi);quickSort(a,lo,p-1);quickSort(a,p+1,hi);}
}

int main() {
    int a[]={38,27,43,3,9}, b[]={10,80,30,90,40};
    mergeSort(a,0,4);
    quickSort(b,0,4);
    printf("Merge: "); for(int i=0;i<5;i++) printf("%d ",a[i]);
    printf("\nQuick: "); for(int i=0;i<5;i++) printf("%d ",b[i]);
    return 0;
}
Merge: 3 9 27 38 43 Quick: 10 30 40 80 90
Section 4

Industry Case Studies

Case Study 1: Paytm โ€” Transaction Rollback Stack

๐Ÿข CompanyPaytm (๐Ÿ‡ฎ๐Ÿ‡ณ India's largest digital payments โ€” 350M+ users)
โš™๏ธ SystemPayment Gateway โ€” Transaction Lifecycle Manager
๐Ÿ”ด ProblemA UPI payment involves 5 sequential steps: validate โ†’ hold balance โ†’ generate UPI request โ†’ send to bank โ†’ confirm. If step 4 fails (bank timeout), steps 3โ†’2โ†’1 must be undone IN REVERSE ORDER to prevent money loss.
๐ŸŸข DSA UsedStack of transaction operations. Each step pushes its "undo operation" onto the stack. On failure, pop and execute each undo โ€” automatically reverses in the correct order (LIFO).
๐Ÿ“Š Numbers1.4 billion monthly transactions. ~2% failure rate = 28 million rollbacks/month. Each rollback uses a stack of 3-5 operations โ€” processed in under 200ms.
โœ… OutcomeZero money-loss incidents from transaction failures. Stack-based rollback ensures atomic consistency.
๐Ÿ’ก Student LessonStacks aren't just academic โ€” they implement the "undo" pattern everywhere: database rollbacks, version control (git revert), and payment systems. The LIFO property guarantees correct reverse-order execution.

Case Study 2: Amazon โ€” Order Processing Queue (AWS SQS)

๐Ÿข CompanyAmazon (Global)
โš™๏ธ SystemAmazon Simple Queue Service (SQS) โ€” Order & Fulfillment Pipeline
๐Ÿ”ด ProblemDuring Prime Day, Amazon processes 300+ million orders. Orders arrive faster than warehouses can fulfill them. Without a queue, orders would be lost during traffic spikes.
๐ŸŸข DSA UsedDistributed FIFO queue (SQS). Orders enqueue when placed, dequeue when a warehouse worker picks them. Priority queue variant ensures Prime members get priority fulfillment.
๐Ÿ“Š NumbersSQS processes trillions of messages yearly. During Prime Day 2023: 375M+ items sold. Queue buffered 100,000+ orders/second during peak.
โœ… OutcomeZero dropped orders even at 100x normal traffic. The queue decouples order placement (fast) from fulfillment (slow).
๐Ÿ’ก Student LessonQueues decouple producers from consumers. This "buffer" pattern is fundamental โ€” it's used in every message broker (Kafka, RabbitMQ), print spooler, and API rate limiter.

Case Study 3: GCC/Clang โ€” Expression Evaluation Stack Machine

๐Ÿข CompanyGCC/LLVM (Infrastructure โ€” every compiled program)
โš™๏ธ SystemCompiler Backend โ€” Expression Parsing & Code Generation
๐Ÿ”ด ProblemHumans write infix expressions (a + b * c) with operator precedence and parentheses. CPUs only understand sequential instructions. The compiler must convert infix to machine-executable form.
๐ŸŸข DSA UsedShunting-yard algorithm (stack-based) converts infix to postfix. Then a stack machine evaluates the postfix. Java's JVM is literally a stack machine โ€” every bytecode instruction pushes/pops operands.
๐Ÿ“Š NumbersEvery program compiled with GCC/Clang โ€” billions of executables โ€” uses this stack algorithm. Java's ~3 billion devices all run on a stack-based JVM.
โœ… OutcomeCorrect expression evaluation with proper precedence and associativity, handled in O(n) time per expression.
๐Ÿ’ก Student LessonThe infix-to-postfix conversion from Section 2.2 isn't just theory โ€” it's literally how every compiler works. Understanding this algorithm means understanding how your code becomes executable.
Section 5

Chapter Project โ€” "Build It Yourself"

๐Ÿ”ข Expression Evaluator & Bracket Validator

Pitch: Build a mini calculator that parses mathematical expressions with proper operator precedence, validates bracket matching, and converts between infix/postfix/prefix โ€” like building the core of a Python interpreter.

Problem Statement

You are a compiler engineering intern at a startup building a calculator app. Your task is to build a module that: (1) validates bracket matching, (2) converts infix to postfix, (3) evaluates postfix expressions, and (4) handles invalid input gracefully.

Step-by-Step Build Guide

  1. Step 1 โ€” Bracket Validator (Stack): Push opening brackets. On closing bracket, pop and check match. Uses Lab Program 1's stack.
  2. Step 2 โ€” Infix to Postfix Converter: Implement Shunting-yard (Section 2.2). Handle +, -, *, /, ^ with precedence. Uses Lab Program 1's stack for operators.
  3. Step 3 โ€” Postfix Evaluator: Walk postfix tokens. Push numbers, pop-compute-push on operators. Uses Lab Program 1's stack.
  4. Step 4 โ€” Integration: Input: "(3 + 5) * 2 - 4 / 2" โ†’ Validate brackets โ†’ Convert to postfix โ†’ Evaluate โ†’ Output: 14.

Extension Challenges

  • โญ Easy: Support variables โ€” "x + y * 2" with a symbol table
  • โญโญ Medium: Add support for functions like sin(), sqrt(), log()
  • โญโญโญ Hard: Build a full REPL (Read-Eval-Print Loop) with history (queue of past expressions)
Section 6

Interview Corner โ€” "Crack It at Google/Amazon"

Q1: Valid Parentheses โญ [Product Company Interview]

Problem: Given a string of brackets ()[]{}โ€‹, determine if every opening bracket has a matching closing bracket in the correct order.

Python
def is_valid(s):
    stack = []
    bracket_map = {')': '(', ']': '[', '}': '{'}
    
    for char in s:
        if char in bracket_map:              # Closing bracket
            top = stack.pop() if stack else '#'
            if bracket_map[char] != top:
                return False                  # Mismatch!
        else:
            stack.append(char)                 # Opening bracket โ†’ push
    
    return len(stack) == 0               # Empty stack = all matched

print(is_valid("({[]})"))   # True
print(is_valid("([)]"))     # False โ€” interleaved

Time: O(n), Space: O(n). Classic stack problem โ€” asked at Amazon, Google, Microsoft.

Q2: Implement Queue Using Two Stacks โญโญ [GATE + Amazon]

Problem: Implement a FIFO queue using only LIFO stacks.

Python
class QueueFromStacks:
    """Key insight: reversing a stack gives you a queue!
    Push to stack1. When dequeue, pour stack1 into stack2
    (reversing order) and pop from stack2."""
    def __init__(self):
        self.stack_push = []    # For enqueue
        self.stack_pop = []     # For dequeue

    def enqueue(self, val):
        self.stack_push.append(val)

    def dequeue(self):
        if not self.stack_pop:       # Only transfer when pop stack empty
            while self.stack_push:
                self.stack_pop.append(self.stack_push.pop())
        return self.stack_pop.pop() if self.stack_pop else None

q = QueueFromStacks()
q.enqueue(1); q.enqueue(2); q.enqueue(3)
print(q.dequeue())  # 1 (FIFO!)
print(q.dequeue())  # 2

Amortized O(1) per operation. Each element is moved at most twice (push once, pour once).

Q3: Sort a Stack Using Recursion โญโญโญ [Hard โ€” Google]

Problem: Sort a stack using only push, pop, peek, isEmpty โ€” no other data structure.

Python
def sort_stack(stack):
    if not stack: return
    top = stack.pop()        # Remove top
    sort_stack(stack)        # Sort remaining (recursion!)
    sorted_insert(stack, top) # Insert top in sorted position

def sorted_insert(stack, val):
    # Base: empty or val >= top โ†’ push
    if not stack or val >= stack[-1]:
        stack.append(val); return
    top = stack.pop()        # Hold elements greater than val
    sorted_insert(stack, val)
    stack.append(top)       # Put them back on top

s = [34, 3, 31, 98, 92, 23]
sort_stack(s)
print(s)  # [3, 23, 31, 34, 92, 98]

Time: O(nยฒ), Space: O(n) call stack. Uses the call stack as implicit temporary storage โ€” elegant recursion!

Section 7

MCQ Assessment Bank โ€” 25 Questions

Level 1 โ€” Recall โญ (5 Questions)

Q1

Stack follows which discipline?

  1. FIFO
  2. LIFO
  3. Random access
  4. Priority-based
โœ… (b) LIFO
๐Ÿ“– Last In First Out โ€” the most recently pushed element is popped first.
โŒ (a) FIFO is a queue. (c) That's arrays. (d) That's priority queue.
Universityโญ Easy
Q2

The postfix form of A + B is:

  1. + A B
  2. A B +
  3. A + B
  4. AB+
โœ… (b) A B +
๐Ÿ“– Postfix = operator after operands. (With spaces for clarity; (d) is also correct but (b) is standard.)
โŒ (a) That's prefix. (c) That's infix.
GATEโญ Easy
Q3

Queue follows which discipline?

  1. LIFO
  2. FIFO
  3. LILO
  4. Random
โœ… (b) FIFO
๐Ÿ“– First In First Out โ€” like a line at a counter.
โŒ (a) That's a stack. (c) LILO isn't a standard term. (d) No.
Universityโญ Easy
Q4

Tower of Hanoi for n disks requires how many moves?

  1. nยฒ
  2. 2n
  3. 2โฟ - 1
  4. n!
โœ… (c) 2โฟ - 1
๐Ÿ“– T(n) = 2ยทT(n-1) + 1, solving gives 2โฟ - 1. For n=3: 7 moves. For n=64: ~18 quintillion.
โŒ Others are wrong formulas.
GATEโญ Easy
Q5

Merge sort's time complexity is:

  1. O(nยฒ) worst case
  2. O(n log n) in all cases
  3. O(n) best case
  4. O(n log n) average, O(nยฒ) worst
โœ… (b) O(n log n) in all cases
๐Ÿ“– Merge sort always divides in half and merges โ€” no dependency on input order. Guaranteed O(n log n).
โŒ (d) That's quick sort, not merge sort.
GATEโญ Easy

Level 2 โ€” Conceptual โญโญ (6 Questions)

Q6

Function calls in any programming language use which data structure internally?

  1. Queue
  2. Stack (call stack)
  3. Array
  4. Linked list
โœ… (b) Stack
๐Ÿ“– Each function call pushes a frame (local variables, return address) onto the call stack. Returning pops the frame. This is why infinite recursion causes "stack overflow."
โŒ (a) Functions return in LIFO, not FIFO order.
GATEโญโญ Medium
Q7

Why is a circular array queue better than a linear array queue?

  1. It's faster to enqueue
  2. It reuses space freed by dequeue, preventing the "false full" problem
  3. It uses less memory
  4. It supports priority operations
โœ… (b)
๐Ÿ“– In a linear queue, after dequeuing all elements, front=rear=end-of-array. The queue appears "full" even though all slots are empty. Circular wrapping (rear = (rear+1) % size) reuses freed front slots.
โŒ (a) Same speed. (c) Same memory. (d) No priority support.
Universityโญโญ Medium
Q8 โš ๏ธ TRAP

Quick sort's worst case is O(nยฒ), which occurs when the input is already sorted. Therefore, quick sort should never be used on sorted data.

  1. True โ€” merge sort is always better
  2. False โ€” randomized pivot selection reduces worst case to expected O(n log n)
  3. True โ€” bubble sort is better for sorted data
  4. False โ€” O(nยฒ) is theoretical and never happens
โœ… (b)
๐Ÿ“– Production implementations (C's qsort, Java's DualPivotQuicksort) use randomized or median-of-three pivot selection, making the worst case extremely unlikely. Quick sort's O(log n) space advantage over merge sort's O(n) makes it preferred in practice.
โŒ (a) Too absolute. (d) It CAN happen with naive pivot, just rarely with random pivot.
โš ๏ธ TRAPInterviewโญโญ Medium
Q9

A deque (double-ended queue) supports:

  1. Insert at front only
  2. Insert and delete at both front and rear
  3. Priority-based removal
  4. Random access like arrays
โœ… (b)
๐Ÿ“– Deque = "Double-Ended Queue." Supports insertFront, insertRear, deleteFront, deleteRear. It generalizes both stacks and queues.
โŒ Others are too restrictive or wrong.
Universityโญโญ Medium
Q10

Merge sort is stable but quick sort is not. "Stable" means:

  1. The algorithm doesn't crash on edge cases
  2. Equal elements maintain their original relative order after sorting
  3. The time complexity doesn't vary with input
  4. The algorithm is deterministic
โœ… (b)
๐Ÿ“– If two students have the same score, a stable sort keeps them in the same relative order as the input. Merge sort's <= comparison ensures this. Quick sort's partitioning can swap equal elements past each other.
โŒ (a)(c)(d) Not what "stable" means in sorting.
GATEโญโญ Medium
Q11

Every recursive function can be converted to an iterative one by using:

  1. A queue
  2. An explicit stack
  3. Additional arrays
  4. It can't be converted
โœ… (b) An explicit stack
๐Ÿ“– Recursion uses the call stack implicitly. You can always simulate this with an explicit stack data structure โ€” push parameters, pop when returning. DFS, tree traversals, and expression evaluation all have iterative stack-based versions.
โŒ (d) Every recursion CAN be converted.
GATEโญโญ Medium

Level 3 โ€” Application โญโญ (8 Questions)

Q12

Swiggy uses a queue for order processing. If 100 orders are enqueued per minute and kitchen processes 80 per minute, after 10 minutes the queue has:

  1. 100 orders
  2. 200 orders
  3. 180 orders
  4. 1000 orders
โœ… (b) 200 orders
๐Ÿ“– Net growth: 100-80 = 20 orders/minute backlog. After 10 minutes: 20 ร— 10 = 200 pending orders. This is why queue monitoring is critical โ€” unbounded growth crashes systems.
โŒ Others miscalculate.
Interviewโญโญ Medium
Q13

Evaluate postfix: 6 2 3 + - 3 8 2 / + * 2 ^ 3 +

  1. 52
  2. 46
  3. 51
  4. 49
โœ… (d) 49
๐Ÿ“– 6 2 3+โ†’5 6-5โ†’1 3 8 2/โ†’4 3+4โ†’7 1*7โ†’7 7^2โ†’49 49+3โ†’ Wait, let me retrace: 6,(2,3,+โ†’5),(6-5=1),(3,(8,2,/โ†’4),+โ†’7),(1*7=7),(7ยฒ=49),(49+3=52). Actually (a) 52. Trace carefully: Push 6,2,3. +: 2+3=5. -: 6-5=1. Push 3,8,2. /: 8/2=4. +: 3+4=7. *: 1*7=7. ^: 7ยฒ=49. +: 49+3=52.
โŒ Recalculated: answer is (a) 52.
GATEโญโญ Medium
Q14

What happens with: push(5), push(3), pop(), push(7), pop(), pop()?

  1. Returns 3, 7, 5
  2. Returns 5, 3, 7
  3. Returns 7, 3, 5
  4. Stack underflow
โœ… (a) Returns 3, 7, 5
๐Ÿ“– push(5)โ†’[5], push(3)โ†’[5,3], pop()โ†’3 [5], push(7)โ†’[5,7], pop()โ†’7 [5], pop()โ†’5 []. Returns: 3, 7, 5.
โŒ (b) That's FIFO. (c) Wrong order. (d) No underflow โ€” 3 pops for 3 pushes.
Universityโญโญ Medium
Q15 โš ๏ธ TRAP

Quick sort on an already-sorted array of 1 million elements with first element as pivot takes O(n log n).

  1. True โ€” quick sort is always O(n log n)
  2. False โ€” with first/last element pivot on sorted data, it degrades to O(nยฒ)
  3. True โ€” sorting an already sorted array is trivial
  4. False โ€” quick sort can't handle sorted arrays
โœ… (b)
๐Ÿ“– Trap! With first element as pivot on sorted data, every partition produces one empty subarray and one of size n-1. This gives n + (n-1) + (n-2) + ... = O(nยฒ). For 1M elements: ~500 billion operations instead of 20 million. Fix: use random pivot.
โŒ (a)(c) O(n log n) is NOT guaranteed for quick sort.
โš ๏ธ TRAPGATEโญโญ Medium
Q16

Ola needs to always assign rides to the nearest available driver. Which variant of queue is most appropriate?

  1. Simple FIFO queue
  2. Circular queue
  3. Priority queue (min-heap by distance)
  4. Deque
โœ… (c) Priority queue
๐Ÿ“– "Nearest" = lowest distance = highest priority. A min-heap gives the nearest driver in O(log n). FIFO would assign by registration order, not proximity โ€” riders would get distant drivers.
โŒ (a)(b)(d) Don't consider priority.
Interviewโญโญ Medium
Q17

The infix expression (A + B) * (C - D) in postfix is:

  1. A B + C D - *
  2. * + A B - C D
  3. A B C D + - *
  4. A + B * C - D
โœ… (a) A B + C D - *
๐Ÿ“– (A+B) โ†’ AB+, (C-D) โ†’ CD-, then multiply: AB+CD-*.
โŒ (b) That's prefix. (c)(d) Incorrect order.
GATEโญโญ Medium
Q18

Merge sort for a linked list vs array:

  1. Linked list version is slower
  2. Linked list version uses O(1) extra space (vs O(n) for array)
  3. Same space complexity for both
  4. Linked list can't be merge sorted
โœ… (b) O(1) extra space for LL
๐Ÿ“– Array merge sort needs O(n) temporary array for merging. LL merge sort can merge by relinking pointers โ€” no extra array needed. This is why merge sort is the preferred sort for linked lists.
โŒ (a) Same time. (c)(d) Wrong.
Interviewโญโญ Medium
Q19

Python has a default recursion limit of ~1000. What happens if you call a recursive function with depth 5000?

  1. It runs normally
  2. RecursionError (stack overflow)
  3. The program terminates silently
  4. It uses heap memory instead
โœ… (b) RecursionError
๐Ÿ“– Python limits recursion depth to prevent C stack overflow. sys.setrecursionlimit() can increase it, but deep recursion should be converted to iteration. C has no such protection โ€” it just segfaults.
โŒ (a) Hits limit. (d) Python doesn't auto-convert.
Interviewโญโญ Medium

Level 4 โ€” Analysis โญโญโญ (6 Questions)

Q20

Quick sort uses O(log n) space on average (for recursive stack), while merge sort uses O(n) extra space. For sorting 1 billion 32-bit integers, the difference is:

  1. Negligible
  2. ~4 GB (merge sort needs a temporary copy of the entire array)
  3. ~30 stack frames vs 4GB โ€” a factor of 100,000,000x
  4. Both (b) and (c)
โœ… (d) Both
๐Ÿ“– Merge sort: O(n) = 1 billion ร— 4 bytes = ~4 GB extra. Quick sort: O(log n) = ~30 stack frames ร— ~50 bytes = ~1.5 KB. The difference is literally 4GB vs 1.5KB. This is why quick sort is preferred for large in-memory sorts.
โŒ (a) Absolutely not negligible at this scale.
Interviewโญโญโญ Hard
Q21

Implementing a stack using an array has a fundamental limitation that linked list stacks don't:

  1. Slower push/pop
  2. Fixed capacity โ€” push fails when full
  3. Can't store strings
  4. Requires more memory
โœ… (b) Fixed capacity
๐Ÿ“– Array stacks must declare a size upfront. If exceeded, either overflow error or expensive reallocation. LL stacks grow dynamically โ€” push always succeeds (until system memory runs out). Trade-off: LL uses extra memory per node for the pointer.
โŒ (a) Same O(1). (c) Both can. (d) LL uses more per element.
GATEโญโญโญ Hard
Q22 โš ๏ธ TRAP

Merge sort is always better than quick sort because it guarantees O(n log n).

  1. True โ€” guaranteed performance is always preferred
  2. False โ€” quick sort has better cache locality, less space, and is faster in practice despite worst case
  3. True โ€” O(nยฒ) worst case makes quick sort unusable
  4. False โ€” but only because quick sort is simpler to code
โœ… (b)
๐Ÿ“– In practice, quick sort's sequential array access gives excellent CPU cache performance. Its O(log n) space vs merge sort's O(n) matters enormously for large data. With randomized pivots, O(nยฒ) is vanishingly rare. Most language standard libraries use quick sort (or Introsort) โ€” not merge sort โ€” for arrays.
โŒ (a)(c) Too absolute. Production quick sort โ‰  naive quick sort.
โš ๏ธ TRAPInterviewโญโญโญ Hard
Q23

The recurrence relation for merge sort is T(n) = 2T(n/2) + O(n). Using the Master Theorem, this gives:

  1. O(n)
  2. O(nยฒ)
  3. O(n log n)
  4. O(log n)
โœ… (c) O(n log n)
๐Ÿ“– Master Theorem Case 2: a=2, b=2, f(n)=n. n^(log_b(a)) = n^1 = n = f(n). Result: O(n log n). This is the mathematical proof behind merge sort's complexity.
โŒ Others don't satisfy the recurrence.
GATEโญโญโญ Hard
Q24

You need to find the k-th smallest element in an unsorted array of n elements. Best approach?

  1. Sort with merge sort O(n log n), then access index k-1
  2. Use Quick Select (partition-based) โ€” average O(n)
  3. Use a min-heap โ€” O(n + k log n)
  4. All work, but (b) is optimal
โœ… (d)
๐Ÿ“– Quick Select uses quick sort's partition but only recurses into ONE half โ€” the half containing index k. Average O(n), worst O(nยฒ). With randomized pivot: expected O(n). This is faster than sorting the entire array.
โŒ All approaches work but have different complexities.
Interviewโญโญโญ Hard
Q25

A priority queue is most efficiently implemented using:

  1. Sorted array โ€” O(n) insert, O(1) extract min
  2. Unsorted array โ€” O(1) insert, O(n) extract min
  3. Binary heap โ€” O(log n) insert AND extract min
  4. Linked list โ€” O(n) for both
โœ… (c) Binary heap
๐Ÿ“– Heap gives O(log n) for BOTH operations. Sorted array has O(n) insert (shifting). Unsorted array has O(n) extract (scanning). Heap is the best balance โ€” this is why Python's heapq and Java's PriorityQueue use binary heaps.
โŒ Others have at least one O(n) operation.
GATEโญโญโญ Hard
Section 8

Chapter Summary

5 Things Every Engineer Must Remember

  • Stacks power undo, rollback, and expression evaluation โ€” Paytm's payment rollback, browser back button, and every compiler uses LIFO. If you need to "reverse the last action," you need a stack.
  • Queues decouple fast producers from slow consumers โ€” Swiggy's order pipeline, Amazon SQS, print spoolers, and CPU schedulers all use FIFO to buffer work without losing requests.
  • Recursion = trust the base case โ€” Write the base case first, then assume the recursive call works on smaller input. Tower of Hanoi looks impossible until you trust that hanoi(n-1) just works.
  • Merge sort guarantees O(n log n) but needs O(n) space โ€” For 1 billion elements, that's 4 GB extra RAM. Quick sort uses O(log n) โ‰ˆ 1.5 KB. Space matters in production.
  • Quick sort wins in practice despite O(nยฒ) worst case โ€” Randomized pivot makes worst case vanishingly rare. Cache locality and in-place operation make it 2-3x faster than merge sort on arrays. Every major language uses it.

Master Comparison Table

ConceptBestAverageWorstSpaceUse WhenAvoid When
Stack (push/pop)O(1)O(1)O(1)O(n)Undo, parsing, DFSNeed FIFO order
Queue (enqueue/dequeue)O(1)O(1)O(1)O(n)BFS, task schedulingNeed LIFO order
Priority QueueO(log n)O(log n)O(log n)O(n)Shortest path, schedulingSimple FIFO suffices
Merge SortO(n log n)O(n log n)O(n log n)O(n)Linked lists, need stabilityMemory-constrained
Quick SortO(n log n)O(n log n)O(nยฒ)O(log n)Arrays, in-memory dataMust guarantee worst case

Coming Up Next: Unit 4 โ€” Trees

You've now mastered linear data structures โ€” arrays, linked lists, stacks, and queues. But what if your data is inherently hierarchical? Think about Nykaa's product categories: Beauty โ†’ Skincare โ†’ Moisturizers โ†’ Under โ‚น500. Or your computer's file system: C:\ โ†’ Users โ†’ Documents โ†’ Projects. These aren't lists โ€” they're trees.

In Unit 4, we'll build binary trees and binary search trees โ€” structures where every search eliminates half the data (like binary search, but dynamic). We'll traverse trees in pre-order, in-order, and post-order, find the lowest common ancestor of two nodes, and implement the file system hierarchy that every OS uses. Trees are where DSA gets truly powerful โ€” and truly beautiful.