Longest Substring Without Repeating Characters — Coding Interview Walkthrough
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.
| Skill | What Interviewers Observe |
|---|---|
| Sliding window recognition | Do you identify that a two-pointer window is the right abstraction? |
| Window contraction logic | Do you correctly advance left when a duplicate is found? |
| Hash map vs. set tradeoff | Do you know when to store indices vs. just membership? |
| Edge case awareness | Do you handle empty strings, all-same characters, and all-unique strings? |
| Complexity analysis | Can you explain why the solution is O(n) despite nested-looking logic? |
| Clean variable management | Do 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:
-
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.
-
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.
-
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. -
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.
-
Is a single character considered a valid substring? Yes, and it’s also a useful sanity check. For
s = "aaaa", the answer should be1, not0. 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
| Approach | Time | Space |
|---|---|---|
| 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
leftdirectly toprevious_index + 1in 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.
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:
- Initialize an empty hash map
char_indexto store{character: most_recent_index}. - Initialize
left = 0as the left boundary of the window. - Initialize
max_length = 0to track the best result seen so far. - Iterate
rightfrom0tolen(s) - 1: - If
s[right]is already inchar_indexand its stored index is>= left, movelefttochar_index[s[right]] + 1. This excludes the duplicate from the window. - Update
char_index[s[right]] = right. Record the latest index of this character. - Update
max_length = max(max_length, right - left + 1). The current window size. - 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] >= leftprevents moving the left pointer backward. This is the most commonly missed detail in implementations. right - left + 1is the current window size, a formula worth stating explicitly so the interviewer can follow your logic.- Updating
char_index[char] = righthappens unconditionally, even whencharisn’t a duplicate. This ensures the map always reflects the freshest position.
Time & Space Complexity
| Operation | Complexity |
|---|---|
| Time | O(n) |
| Space | O(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
-
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.
-
Forgetting the
char_index[char] >= leftguard. Without this check, a character that appeared before the current window would incorrectly pushleftbackward, breaking the window invariant. This is the single most common subtle bug in this problem. -
Not updating
char_indexafter movingleft. 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. -
Computing window size as
right - leftinstead ofright - left + 1. Off-by-one. A window whereleft == rightcontains one character, so its length is1, not0. -
Not handling the empty string. If
s = "", the loop never executes andmax_lengthstays0, which is correct. But it’s worth verifying this edge case explicitly in your head (or out loud) before submitting. -
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.
-
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 expandrightone character at a time. If the new character is already in the window, I advanceleftto 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
leftforward, 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.
Related Problems to Practice
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:
- Longest Substring with At Most Two Distinct Characters (LeetCode #159). Direct extension: same window, but you allow up to 2 unique chars before shrinking.
- Longest Substring with At Most K Distinct Characters (LeetCode #340). Generalization of #159. Parameterize the distinct character limit.
- Minimum Window Substring (LeetCode #76). Sliding window with a frequency constraint: find the smallest window containing all target chars.
- Find All Anagrams in a String (LeetCode #438). Fixed-size sliding window. Check if the window matches a target frequency profile.
- Permutation in String (LeetCode #567). Same fixed-size window pattern as #438. Checks for any permutation of a pattern in the string.
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:
- How to Prepare for a Coding Interview in 2026, the complete roadmap and study plans
- Why LeetCode alone isn’t enough, and what to practice instead
- Practice any LeetCode problem as a live mock interview
- The Grind 75 Pathway, a structured study plan with AI practice links
- Browse all walkthroughs on GitHub, condensed solutions with code