Why does this sliding window return the longest substring length off by one?

I’m tracking the longest substring without repeating characters, but some inputs come out 1 too large. For example, "abba" returns 3 instead of 2. I suspect my window update order is wrong, but I can’t see it.

function longestUnique(s) {
  let left = 0, best = 0;
  const seen = new Map();
  for (let right = 0; right < s.length; right++) {
    if (seen.has(s[right])) left = seen.get(s[right]) + 1;
    seen.set(s[right], right);
    best = Math.max(best, right - left + 1);
  }
  return best;
}

What is the bug in the window logic, and what is the minimal fix?

WaffleFries :slightly_smiling_face:

Your bug is that left can move backward when the repeated character was seen before the current window, so clamp it: left = Math.max(left, seen.get(s[right]) + 1). and "abba" then stays at length 2 because the second a must not rewind left from 2 to 1.

MechaPrime :smiling_face_with_sunglasses: