Back to DSA & Algorithms
DSA & Algorithms
easy
mid

How would you find the longest substring without repeating characters using a sliding window?

Sliding window with a Map<char, lastIndex>. Move right pointer through the string; when the current char's last index is within the window, jump left to one past it; track max window size as you go. O(n) time, O(min(n, alphabet)) space.

3 min read·~15 min to think through

Classic sliding window problem. Two cleanest implementations: with a Set (shrink one at a time) or with a Map of last indices (jump left in O(1)).

Implementation — Map of last indices

js
function lengthOfLongestSubstring(s) {
  const lastIndex = new Map();
  let left = 0, best = 0;

  for (let right = 0; right < s.length; right++) {
    const ch = s[right];
    if (lastIndex.has(ch) && lastIndex.get(ch) >= left) {
      left = lastIndex.get(ch) + 1;     // jump past the previous occurrence
    }
    lastIndex.set(ch, right);
    best = Math.max(best, right - left + 1);
  }

  return best;
}
  • Time O(n) — each right advances once; left only ever moves forward.
  • Space O(min(n, |Σ|)) — bounded by alphabet size.

Why the >= left check

lastIndex may remember a duplicate from before the window. If lastIndex.get(ch) < left, it's out of the window — ignore it. Without that check, left could go backward, breaking the invariant.

Alternative — Set, shrink one at a time

js
function lengthOfLongestSubstring(s) {
  const inWindow = new Set();
  let left = 0, best = 0;
  for (let right = 0; right < s.length; right++) {
    while (inWindow.has(s[right])) {
      inWindow.delete(s[left++]);
    }
    inWindow.add(s[right]);
    best = Math.max(best, right - left + 1);
  }
  return best;
}

Same O(n) (each char enters and leaves at most once), simpler invariant, slightly slower in practice.

Walkthrough — "abcabcbb"

ts
right=0 'a' → window [0,0] "a" → best 1
right=1 'b' → window [0,1] "ab" → best 2
right=2 'c' → window [0,2] "abc" → best 3
right=3 'a''a' last at 0, in window → left = 1"bca" → best 3
right=4 'b''b' last at 1, in window → left = 2"cab" → best 3
right=5 'c''c' last at 2, in window → left = 3"abc" → best 3
right=6 'b''b' last at 4, in window → left = 5"cb" → best 3
right=7 'b''b' last at 6, in window → left = 7"b" → best 3

Answer: 3.

Edge cases

  • Empty string → 0.
  • All same characters ("aaaa") → 1.
  • All distinct ("abcdef") → length.
  • Single character → 1.
  • Unicode / multi-byte — iterate by code points if you care about emoji/surrogate pairs: for (const ch of s).

Variants

  • Longest substring with at most K distinct characters — same shape, condition on Map size.
  • Longest substring with at most K repeating characters — track counts.
  • Smallest window containing all chars of T — minimum window substring.

Interview framing

"Sliding window with a Map of char → last index. Move right through the string. When the current char's last seen index is within the window (>= left), advance left to one past it — that jump is what makes this O(n) instead of O(n²). Update the char's last index and the best window size. The key check is lastIndex >= left — stale entries from before the window must be ignored. O(n) time, O(min(n, alphabet)) space."

Follow-up questions

  • Why the `>= left` check?
  • How does this generalize to 'at most K distinct characters'?
  • Walk through with 'abba'.
  • How would you handle Unicode (emoji, surrogate pairs)?

Common mistakes

  • Not checking `lastIndex >= left` — left moves backward, wrong answer.
  • Updating best inside the if-branch — misses the case where no duplicate is found.
  • Off-by-one in window length (right - left vs right - left + 1).
  • Using indexOf inside the loop — O(n²).

Performance considerations

  • O(n) time, O(k) space where k = alphabet. Each index advances at most once.

Edge cases

  • Empty string.
  • Single char.
  • All identical chars.
  • Unicode: iterate by code points.

Real-world examples

  • Substring search optimizations.
  • Detecting unique-character runs in streaming data.

Senior engineer discussion

Seniors choose the Map-of-last-index variant for the O(1) jump, articulate the `>= left` invariant, handle Unicode correctly, and recognize the pattern as a generalizable sliding-window technique.

Related questions