Three problems, worked end to end

The full process applied — clarify, brute force, improve, code, test.

Problem 1 — longest substring without repeating characters

Clarify. Substring means contiguous. Return the length. Characters could be any Unicode. Empty string returns 0.

Brute force. Every substring, checking each for duplicates: O(n³), or O(n²) with a set per start. Say it, then improve.

The insight. As the right edge advances, the left edge never needs to move backwards — so it is a sliding window, O(n).

function longestUnique(text) {
  const lastSeen = new Map();
  let start = 0;
  let best = 0;

  for (let end = 0; end < text.length; end++) {
    const ch = text[end];

    if (lastSeen.has(ch) && lastSeen.get(ch) >= start) start = lastSeen.get(ch) + 1;

    lastSeen.set(ch, end);
    best = Math.max(best, end - start + 1);
  }

  return best;
}

Test. "" → 0. "aaaa" → 1. "abcabcbb" → 3. "tmmzuxt" → 5 — that last one catches the missing >= start guard, which is the standard bug.

Problem 2 — number of islands

Clarify. Four-directional adjacency. May I mutate the grid? (If yes, no visited set is needed.)

Approach. Every unvisited land cell starts a flood fill; count the fills.

function countIslands(grid) {
  let islands = 0;

  const sink = (r, c) => {
    if (r < 0 || c < 0 || r >= grid.length || c >= grid[0].length) return;
    if (grid[r][c] !== '1') return;

    grid[r][c] = '0';                        // mark visited by sinking it
    sink(r + 1, c); sink(r - 1, c); sink(r, c + 1); sink(r, c - 1);
  };

  for (let r = 0; r < grid.length; r++) {
    for (let c = 0; c < grid[0].length; c++) {
      if (grid[r][c] === '1') { islands++; sink(r, c); }
    }
  }

  return islands;
}

O(rows × cols) time and, in the worst case, the same in recursion depth — mention that a BFS queue avoids the stack overflow on a large all-land grid.

Problem 3 — k-th largest in a stream

Clarify. Repeated queries as values arrive, so this is a design question, not a one-off scan.

Approach. A min-heap of size k. The root is always the k-th largest; anything smaller than the root is irrelevant forever.

class KthLargest {
  constructor(k, initial = []) {
    this.k = k;
    this.heap = new MinHeap();
    for (const value of initial) this.add(value);
  }

  add(value) {
    if (this.heap.size < this.k) this.heap.push(value);
    else if (value > this.heap.peek()) { this.heap.pop(); this.heap.push(value); }

    return this.heap.peek();
  }
}

O(log k) per insertion, O(k) memory regardless of stream length. Compare aloud with sorting (O(n log n) per query, and needs the whole stream) — naming the alternative and why you rejected it is the part that scores.