Lesson 62 of 77 intermediate

DSA II: Stack, Queue, Linked List, Binary Search & Heap

Linear data structures and search algorithms — patterns for undo systems, BFS, and top-K problems

Open interactive version (quiz + challenge)

Real-world analogy

A Stack is like a pile of plates — you always add and remove from the top. A Queue is like a checkout line — first in, first out. A Heap is like a VIP queue — the most important person always gets served first, regardless of arrival order.

What is it?

Stack, Queue, LinkedList, binary search, and heap are intermediate data structures that underpin common interview patterns — undo systems, BFS, sorted data search, and top-K selection — all of which have direct parallels in mobile app development.

Real-world relevance

Tixio's message editor uses a stack for undo history. The offline sync queue in FieldBuzz is a FIFO queue processed in order. Binary search finds a message by timestamp in the local SQLite cache sorted by createdAt. A priority queue ranks notifications by urgency in the notification triage system.

Key points

Code example

// Stack: bracket/parenthesis validation
bool isValid(String s) {
  final stack = <String>[];
  const pairs = {'(': ')', '[': ']', '{': '}'};
  for (final c in s.split('')) {
    if (pairs.containsKey(c)) {
      stack.add(c);
    } else {
      if (stack.isEmpty || pairs[stack.last] != c) return false;
      stack.removeLast();
    }
  }
  return stack.isEmpty;
}

// Binary search: find message by timestamp in sorted list
int findByTimestamp(List<int> timestamps, int target) {
  int left = 0, right = timestamps.length - 1;
  while (left <= right) {
    final mid = left + (right - left) ~/ 2;
    if (timestamps[mid] == target) return mid;
    if (timestamps[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return -1; // not found
}

// Heap pattern: top-K most active channels
// Using sorted list as simple min-heap simulation
List<String> topKChannels(Map<String, int> activity, int k) {
  final entries = activity.entries.toList()
    ..sort((a, b) => b.value.compareTo(a.value));
  return entries.take(k).map((e) => e.key).toList();
}

// Linked list: reverse (iterative)
class ListNode {
  int val;
  ListNode? next;
  ListNode(this.val, [this.next]);
}

ListNode? reverseList(ListNode? head) {
  ListNode? prev, curr = head;
  while (curr != null) {
    final next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
  }
  return prev;
}

// Fast/slow pointers: find middle of linked list
ListNode? findMiddle(ListNode? head) {
  ListNode? slow = head, fast = head;
  while (fast?.next != null) {
    slow = slow?.next;
    fast = fast?.next?.next;
  }
  return slow;
}

Line-by-line walkthrough

  1. 1. isValid iterates each character of the string once — O(n)
  2. 2. Opening brackets push to stack; closing brackets must match the top
  3. 3. pairs map connects each opener to its expected closer
  4. 4. If stack is empty when a closer arrives — no matching opener — return false
  5. 5. If stack top doesn't match the closer — mismatched pair — return false
  6. 6. At the end, empty stack confirms all openers were closed
  7. 7. findByTimestamp implements standard binary search on sorted timestamps
  8. 8. mid = left + (right-left)~/2 avoids integer overflow vs (left+right)~/2
  9. 9. reverseList iterates once, reversing next pointers in-place — O(n) O(1) space
  10. 10. prev starts null (new tail), curr walks forward, next saves the link before overwriting
  11. 11. findMiddle advances fast 2x speed — when fast hits the end, slow is at middle
  12. 12. This is O(n) time O(1) space — optimal for finding list midpoint

Spot the bug

// Binary search implementation
int binarySearch(List<int> arr, int target) {
  int left = 0, right = arr.length;
  while (left < right) {
    int mid = (left + right) ~/ 2;
    if (arr[mid] == target) return mid;
    if (arr[mid] < target) left = mid;
    else right = mid - 1;
  }
  return -1;
}
Need a hint?
This has two bugs that cause either infinite loops or incorrect results. Trace through [1,3,5,7], target=7.
Show answer
Bug 1: right = arr.length should be arr.length - 1 — otherwise arr[mid] can access out-of-bounds when left==right==length. Bug 2: left = mid should be left = mid + 1 — when arr[mid] < target, setting left=mid instead of mid+1 causes an infinite loop when left and right are adjacent (mid never advances). Fix: int right = arr.length - 1 and left = mid + 1.

Explain like I'm 5

A stack is like a stack of pancakes — you add and eat from the top. A queue is like a line at a theme park — whoever joins first gets in first. Binary search is like guessing a number: instead of counting from 1, you always guess the middle and ask 'higher or lower?' — you find the number in just a few guesses.

Fun fact

The call stack in every programming language — including Dart — is literally a stack data structure. Every function call pushes a frame; every return pops it. Stack overflow errors happen when the stack grows beyond its memory limit, usually from infinite recursion.

Hands-on challenge

Implement a simplified undo system for a text editor: push state on every edit, pop on undo. Then extend it to support redo (you'll need two stacks). Test with: type 'H', type 'e', type 'l', undo, undo, redo — what is the text?

More resources

Open interactive version (quiz + challenge) ← Back to course: Flutter Interview Mastery