📖 11 min read
Last updated on

Longest Substring Without Repeating Characters — Coding Interview Walkthrough


Hero Image for Longest Substring Without Repeating Characters

You’re in a coding interview. The interviewer says: “Given a string, find the length of the longest substring without repeating characters.” You know it’s a sliding window problem. You’ve solved it before. But now you need to explain why a hash map of indices beats a simple set, implement the char_index[char] >= left guard without hesitating, and handle the follow-up about “at most k distinct characters” while someone watches. That’s a very different challenge.

This walkthrough covers how that conversation unfolds: the clarifying questions worth asking, the precise window contraction logic interviewers want to see, and the subtle bugs that catch even prepared candidates.


The Problem

Given a string s, find the length of the longest substring that contains no repeating characters. A substring is a contiguous sequence of characters within the string, not to be confused with a subsequence, which can skip characters. (LeetCode #3)

Example

Input

s = "abcabcbb"

Output

3

The longest substring without repeating characters is "abc", which has length 3. Note that "abcabc" doesn’t qualify because 'a', 'b', and 'c' each repeat. A second example: for s = "bbbbb", every character is a repeat, so the answer is 1 (any single character). For s = "pwwkew", the answer is 3, the substring "wke".

Already comfortable with the solution? Practice it in a mock interview →


What Interviewers Are Actually Testing

This problem is a direct filter for sliding window intuition. Interviewers aren’t just checking whether you arrive at the right answer. They’re watching how you reason about window boundaries and what data structure you choose to track the window’s contents.

SkillWhat Interviewers Observe
Sliding window recognitionDo you identify that a two-pointer window is the right abstraction?
Window contraction logicDo you correctly advance left when a duplicate is found?
Hash map vs. set tradeoffDo you know when to store indices vs. just membership?
Edge case awarenessDo you handle empty strings, all-same characters, and all-unique strings?
Complexity analysisCan you explain why the solution is O(n) despite nested-looking logic?
Clean variable managementDo you correctly update max_length at every valid window state?

Step 1: Clarifying Questions

Taking 60 seconds to ask smart questions before coding signals maturity and prevents wasted effort. Here are the most important ones for this problem:

  1. Does the string contain only lowercase English letters, or can it include uppercase, digits, and special characters? LeetCode allows any ASCII character. This affects your choice of data structure: a fixed-size array of 128 works for ASCII, but a hash map is more general and safer to default to in an interview.

  2. Should I return the length of the substring, or the substring itself? The problem asks for the length. Confirming this avoids building unnecessary bookkeeping to track the actual characters.

  3. What should I return for an empty string? An empty string should return 0. Explicitly handling this edge case, even if your code handles it naturally, shows defensive thinking.

  4. Are there constraints on string length that hint at the expected complexity? LeetCode’s constraints go up to 50,000 characters. This rules out O(n²) solutions in production contexts and confirms that O(n) is the target.

  5. Is a single character considered a valid substring? Yes, and it’s also a useful sanity check. For s = "aaaa", the answer should be 1, not 0. Verifying this base case upfront keeps your solution grounded.


Step 2: A First (Non-Optimal) Idea

The most natural starting point is brute force: generate every possible substring, check each one for duplicate characters, and track the longest valid one.

def lengthOfLongestSubstring(s):
    max_length = 0
    for i in range(len(s)):
        for j in range(i + 1, len(s) + 1):
            substring = s[i:j, "medium"]
            if len(substring) == len(set(substring)):  # No duplicates
                max_length = max(max_length, len(substring))
    return max_length
ApproachTimeSpace
Brute force (all substrings)O(n³)O(min(n, m))

This is O(n³) in time: O(n²) pairs of (i, j) multiplied by O(n) to build the set for each substring. Even with micro-optimizations (early termination when a duplicate is found), this stays O(n²) at best. For a 50,000-character string, that’s 2.5 billion operations.

Present this approach first to show structured thinking, then pivot: “This works but is far too slow. The key inefficiency is that we’re re-examining characters we’ve already seen. A sliding window lets us process each character at most twice.”


Step 3: The Key Insight

You don’t need to restart from scratch every time you find a duplicate. You just need to advance the left boundary of your window past the previous occurrence of that character. If you store each character’s most recent index in a hash map, you can jump left directly to previous_index + 1 in a single O(1) operation.

Picture a window [left, right] representing your current candidate substring. As you expand right to include new characters, everything is fine until you hit a character that already exists inside the window. At that point, instead of abandoning the window entirely, you slide left forward just enough to exclude the earlier occurrence of the duplicate.

This is the heart of the sliding window pattern: a window that only ever moves forward, processing each character at most twice (once when right includes it, once when left passes it), giving O(n) total.

One subtlety worth noting: when a duplicate is found, you should only move left forward, never backward. If the stored index of a duplicate is already to the left of the current left, you shouldn’t move left back, as that would re-introduce characters you’ve already excluded.

Sliding window trace for abcabcbb showing left pointer, window size, and max_length at each step

The highlighted columns show the three moments where left jumps forward to exclude a duplicate. Notice how max_length locks in at 3 after index 2 and never decreases, while window size shrinks and recovers as the window contracts past each repeated character.


Step 4: Optimal Strategy

Here’s the sliding window algorithm, step by step:

  1. Initialize an empty hash map char_index to store {character: most_recent_index}.
  2. Initialize left = 0 as the left boundary of the window.
  3. Initialize max_length = 0 to track the best result seen so far.
  4. Iterate right from 0 to len(s) - 1:
  5. If s[right] is already in char_index and its stored index is >= left, move left to char_index[s[right]] + 1. This excludes the duplicate from the window.
  6. Update char_index[s[right]] = right. Record the latest index of this character.
  7. Update max_length = max(max_length, right - left + 1). The current window size.
  8. After the loop, return max_length.

The condition char_index[s[right]] >= left in step 5 is critical. Without it, you might accidentally move left backward when a character appeared in a previous window that’s already been slid past.


Python Implementation

def lengthOfLongestSubstring(s: str) -> int:
    char_index = {}  # Maps character -> its most recent index in s
    max_length = 0
    left = 0         # Left boundary of the current window

    for right in range(len(s)):
        char = s[right, "medium"]

        # If char is in our window (not just in the map), shrink from the left
        if char in char_index and char_index[char] >= left:
            left = char_index[char] + 1  # Jump past the previous occurrence

        # Always update to the latest index of this character
        char_index[char] = right

        # Update the longest valid window seen so far
        max_length = max(max_length, right - left + 1)

    return max_length

Style notes worth calling out in an interview:

  • Storing the character’s index (not just a boolean) is what enables the O(1) left-pointer jump. If you used a set, you’d need a while loop to shrink the window character by character. Still O(n) amortized, but less elegant and harder to explain.
  • The guard char_index[char] >= left prevents moving the left pointer backward. This is the most commonly missed detail in implementations.
  • right - left + 1 is the current window size, a formula worth stating explicitly so the interviewer can follow your logic.
  • Updating char_index[char] = right happens unconditionally, even when char isn’t a duplicate. This ensures the map always reflects the freshest position.

Time & Space Complexity

OperationComplexity
TimeO(n)
SpaceO(min(n, m))

Time: The right pointer traverses the string once from left to right, n steps total. The left pointer only ever moves forward and never exceeds right. Every character is “entered” into the window once and “exited” at most once. Each hash map operation is O(1). Total: O(n).

Space: The hash map stores at most one entry per unique character in the string. That’s bounded by min(n, m) where m is the size of the character set (128 for ASCII, 26 for lowercase letters). In the worst case (all unique characters) the map grows to O(n).

This is a common interview follow-up: “Can you reduce space further?” For lowercase letters only, you could use a fixed-size array of 26 instead of a hash map, O(1) space relative to input size, though still O(m) in absolute terms.


Common Interview Mistakes

  1. Using a set instead of a hash map. A set tells you if a character is in the window but not where it last appeared. With a set, you’d need a while loop to shrink the window one character at a time. Correct, but slower to write and explain. The index-storing hash map is cleaner and more impressive.

  2. Forgetting the char_index[char] >= left guard. Without this check, a character that appeared before the current window would incorrectly push left backward, breaking the window invariant. This is the single most common subtle bug in this problem.

  3. Not updating char_index after moving left. The map must always reflect the most recent index of every character. Skipping the update causes stale indices and wrong left-pointer jumps on subsequent iterations.

  4. Computing window size as right - left instead of right - left + 1. Off-by-one. A window where left == right contains one character, so its length is 1, not 0.

  5. Not handling the empty string. If s = "", the loop never executes and max_length stays 0, which is correct. But it’s worth verifying this edge case explicitly in your head (or out loud) before submitting.

  6. Coding the sliding window before explaining it. The window contraction logic is non-obvious enough that writing it silently looks like guesswork. Always say “when I hit a duplicate, I’ll advance left past its previous occurrence” before your fingers touch the keyboard.

  7. Confusing substring with subsequence. A substring must be contiguous. Candidates who’ve been thinking about subsequence problems sometimes blur this line. Confirming “contiguous characters” early prevents building the wrong solution entirely.


What a Strong Candidate Sounds Like

“My first thought is brute force: check every substring for duplicates. That’s O(n³), which doesn’t scale. The inefficiency is that I’m re-examining characters I’ve already processed.

A better approach is a sliding window. I’ll maintain a window [left, right] representing my current candidate substring. I expand right one character at a time. If the new character is already in the window, I advance left to just past the previous occurrence of that character. No need to restart from scratch.

To make the left-pointer jump O(1), I’ll store each character’s most recent index in a hash map. One subtlety: I only move left forward, never backward, so I guard against stale map entries by checking that the stored index is still inside the current window.

This gives me O(n) time since both pointers only move forward, and O(min(n, m)) space for the map. Let me trace through "abcabcbb" to verify before coding.”


Example Interview Dialogue

Interviewer: Find the longest substring without repeating characters.

Candidate: Before I start: should I return the length, or the actual substring? And can the input contain any ASCII characters, or just lowercase letters?

Interviewer: Return the length. Any ASCII characters.

Candidate: Got it. Brute force would be O(n²) or O(n³), too slow. I’ll use a sliding window with a hash map storing each character’s latest index. As I move right forward, if I hit a character that’s already in the window, I jump left to one past its last known position. That shrinks the window to remove the duplicate in O(1).

Interviewer: What if the same character appears, but its last occurrence was before the current left?

Candidate: Great edge case. I guard against that with char_index[char] >= left before moving left. If the stored index is already outside the window, I don’t move left at all. I just update the map and continue. Without that guard, I’d accidentally move left backward.

Interviewer: What’s the time complexity?

Candidate: O(n). Both left and right only ever move forward. Each character is visited at most twice across the entire traversal. Every map operation is O(1), so the total is linear. Space is O(min(n, m)) for the map, where m is the character set size.

Interviewer: How would you modify this to find the longest substring with at most k distinct characters?

Candidate: Instead of jumping left on any duplicate, I’d track a count of distinct characters in the window. Whenever the count exceeds k, I’d shrink from the left until it’s back to k. A hash map of {char: frequency} would work. When a character’s frequency drops to zero, I remove it, which decrements the distinct count. Same O(n) complexity.


Longest Substring Without Repeating Characters is the entry point to the sliding window family. These problems all use the same expand-and-contract window logic:

The jump from #3 to #76 is one of the most common interview escalations. Both use hash maps and two pointers, but minimum window adds a “must contain all of these” constraint that requires careful frequency tracking.


Practice This in a Mock Interview

Working through this explanation gets you to the right mental model. But the sliding window is one of those patterns where the gap between “I understand it” and “I can implement it cleanly on the spot” is wide.

The char_index[char] >= left guard, the correct window size formula, the unconditional map update: these are the kinds of details that feel obvious when you’re reading calmly and evaporate under the pressure of a live interview. The only way to make them automatic is deliberate repetition under realistic conditions.

Start a mock interview for Longest Substring Without Repeating Characters on Intervu.

Further reading:

Practice Like It's the Real Interview

Get instant feedback on your approach, communication, and code — powered by AI.

Start a Mock Interview →