Interview Q&A / Data Structures & Algorithms

AdvancedIn-depthCoding4 min readUpdated 2 Oct 2026

How do you find the longest substring without repeating characters?

SHORT ANSWER

Use a sliding window. Expand the right end one character at a time and remember the last index where each character was seen. If the new character already appears inside the window, move the left end just past its previous position. Track the largest window size. This is O(n) time.

Explain it simply

Picture a train with coaches labelled by letters, and you want the longest run of consecutive coaches where no letter repeats. You hold a frame that you slide along the train. You keep stretching the right edge of the frame to include the next coach. The moment a letter appears twice inside the frame, you pull the left edge forward just past the earlier copy of that letter, so the frame is valid again. Along the way, you note the widest the frame ever got.

You never go back and restart from scratch. Each coach enters the frame once and leaves at most once. That is why it is fast.

Line by line

js
function lengthOfLongestSubstring(s) {
  const lastSeen = new Map(); // character -> last index where it appeared
  let left = 0; // start of the current window
  let best = 0;

  for (let right = 0; right < s.length; right++) {
    const ch = s[right];
    // If ch was seen inside the current window, jump left past it
    if (lastSeen.has(ch) && lastSeen.get(ch) >= left) {
      left = lastSeen.get(ch) + 1;
    }
    lastSeen.set(ch, right); // update the latest position of ch
    best = Math.max(best, right - left + 1); // window is s[left..right]
  }
  return best;
}

console.log(lengthOfLongestSubstring("abcabcbb")); // Output: 3
console.log(lengthOfLongestSubstring("bbbbb")); // Output: 1
console.log(lengthOfLongestSubstring("pwwkew")); // Output: 3
console.log(lengthOfLongestSubstring("abba")); // Output: 2
console.log(lengthOfLongestSubstring("")); // Output: 0
console.log(lengthOfLongestSubstring("dvdf")); // Output: 3

Trace for "pwwkew":

rightcharleft after checkwindowbest
0p0p1
1w0pw2
2w2 (old w at 1)w2
3k2wk2
4e2wke3
5w3 (old w at 2)kew3

The answer is 3 ("wke" or "kew"). Note "pwke" is a subsequence, not a substring, so it does not count.

How it works

  • The window s[left..right] always contains distinct characters. That is the invariant.
  • When a repeat arrives, all positions up to and including its previous copy can never be part of a valid window ending at right, so left jumps straight past it.
  • The check lastSeen.get(ch) >= left matters. Look at "abba": when the second a arrives at index 3, left is already 2 (it moved past the first b). The old a at index 0 is outside the window, so left must not move back to 1. Without this check the answer would wrongly be 3.

Approaches, from slow to fast:

  1. Brute force: check every substring and test if its characters are unique. O(n³), or O(n²) with a set.
  2. Sliding window with a set: move left forward one step at a time, removing characters, until the repeat is gone. O(n), each character added and removed at most once.
  3. Sliding window with last-seen indexes (above): left jumps directly. Still O(n), with fewer steps.

Complexity

ApproachTimeExtra space
Brute forceO(n³)O(k)
Sliding window with setO(n)O(k)
Sliding window with last index mapO(n)O(k)

k is the number of distinct characters possible, at most the size of the alphabet (for example 128 for ASCII), so the space is often described as O(min(n, k)).

Common mistakes

  • Letting left move backwards, as in the "abba" case. Either check that the old index is inside the window, or write left = Math.max(left, lastSeen.get(ch) + 1).
  • Off-by-one on the length. The window from left to right inclusive has right - left + 1 characters.
  • Confusing substring (contiguous) with subsequence (any order-preserving pick).
  • Restarting the scan from left each time a repeat is found, which turns the solution back into O(n²).

What to say in the interview

"I use a sliding window with two pointers and a map from each character to the last index where I saw it. I move the right pointer through the string. If the current character was last seen at or after left, I move left to one past that index, so the window stays free of repeats. Then I record the character's new index and update the best length as right minus left plus 1. Each character is processed once, so it is O(n) time and O(k) space, where k is the alphabet size."

Follow-up questions

  • How would you return the substring itself, not just its length?
  • What is the longest substring with at most k distinct characters?
  • How do you find the smallest window that contains all characters of another string?
  • Why is the sliding window O(n) when there is a loop inside a loop in the set version?

Go deeper

Read these once the basics above feel easy.

To return the substring itself, store the left value whenever best improves, then return s.slice(bestLeft, bestLeft + best).

The same sliding window pattern solves a family of problems. Expand the right edge, and shrink from the left whenever the window breaks a rule. For "at most k distinct characters", keep a count of each character in the window and shrink while the map has more than k keys.

#strings#sliding-window#hashing#two-pointers

Related questions

Practise more Data Structures & Algorithms questions

Test your recall with short answers, then return to the detailed explanations when you need context.

Open Data Structures & Algorithms quick answers →

Get new questions like this by email

One short email when something new and useful lands. No spam, unsubscribe in one tap.

By subscribing you agree to the Privacy Policy.