How to Explain Longest Substring Without Repeating Characters in a Coding Interview (Step-by-Step)
This problem is the canonical introduction to the sliding window pattern. The brute-force answer is obvious; the O(n) insight is not. The key is understanding why you never need to shrink the window one character at a time — and how a HashMap lets you jump the left pointer directly to the right position.
In this post, you’ll learn exactly how to walk an interviewer through LeetCode 3: the sliding window setup, the HashMap character-index trick, the left-pointer jump optimisation, and a clean complexity argument.
Problem Statement
Given a string s, find the length of the longest substring without repeating characters.
Note: A substring is a contiguous sequence of characters — not a subsequence.
Examples
Output: 3
Explanation: “abc” is the longest substring without repeating characters.
Output: 1
Explanation: “b” is the longest such substring.
Output: 3
Explanation: “wke” is the longest — note “pwke” is a subsequence, not a substring.
Constraints
0 <= s.length <= 5 * 104sconsists of English letters, digits, symbols, and spaces
What Is the Problem?
“I need to find the length of the longest contiguous chunk of the string where every character appears exactly once. I’m not rearranging anything — the characters must come from a consecutive run within the original string. And I need to call out that ‘substring’ means contiguous, not subsequence.”
Explicitly distinguishing substring from subsequence is a small but powerful signal — it tells the interviewer you’ve read the constraint carefully and won’t accidentally solve the wrong problem.
Step 1: Compare Approaches Before Jumping to Code
“Both the set and HashMap approaches are O(n) in terms of big-O, but the HashMap is the cleaner, more elegant solution — it avoids shrinking the window character by character and makes the logic much easier to reason about in an interview.”
The Key Insight — Store Index, Not Just Presence
Maintain a sliding window defined by two pointers: left and right. As right expands through the string, store each character’s most recent index in a HashMap.
When s[right] is already in the map and its stored index is within the current window (i.e., >= left), jump left directly to stored_index + 1. This skips over the duplicate in one step — no loop needed.
After adjusting left, update the map with the new index of s[right] and update the maximum window length.
“The critical guard is checking that the stored index is within the current window before jumping. If the previous occurrence of a character is already to the left of our window, it’s no longer a conflict — we should ignore it and not move left backwards.”
Step 2: Explain the Algorithm in Plain English
Set left = 0, max_len = 0, and an empty HashMap char_index that maps each character to its most recently seen index.
right through the stringFor each index right, look up s[right] in the map. If it exists and its stored index is >= left, a duplicate is inside the window — jump left to char_index[s[right]] + 1.
Always update char_index[s[right]] = right (overwrite the old index). Then update max_len = max(max_len, right - left + 1).
After right reaches the end of the string, return max_len. Every character was visited exactly once — total work is O(n).
Sliding Window Visualised — s = “abcabcbb”
Tracing through the string step by step. The window is always the range [left, right]. The HashMap stores each character’s last-seen index.
Step 3: Talk Through the Code
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
char_index = {} # Maps each character to its most recently seen index
left = 0
max_len = 0
for right in range(len(s)):
char = s[right]
# If char is in the window (stored index >= left), jump left past it
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
# Always update the character's latest index
char_index[char] = right
# Update max window length
max_len = max(max_len, right - left + 1)
return max_len
Highlight the guard char_index[char] >= left — this is essential. Without it, a character seen before the current window would incorrectly push left backward (or not forward enough). The map retains old entries but we only act on them if they’re still inside the active window.
The Left-Jump Optimisation — Why It Matters
With a HashSet, when a duplicate is found you must remove characters from the left one by one until the duplicate is gone — potentially O(n) work per character in the worst case (even though the amortised total is still O(n)). With a HashMap storing indices, you jump left directly in a single operation — conceptually cleaner and genuinely faster in practice.
“If the interviewer asks ‘could you do this with a set instead?’ — yes, and it’s also O(n) amortised. But the HashMap version is the one worth knowing: it’s cleaner to explain, harder to get wrong, and eliminates an inner while loop entirely.”
Step 4: Highlight Edge Cases
“Ifs = "", the for loop never runs, and we returnmax_len = 0. Correct — the constraint allows length 0.”
“Every timerightadvances, it finds ‘b’ in the map with indexright - 1, which is always>= left. Soleftjumps torighteach time — the window stays size 1 throughout. Answer: 1.”
“No character is ever found in the map while inside the window. The window expands with every step of right and never shrinks. The answer is the length of the entire string.”
“Whenrightreaches the final ‘t’, the map has ‘t’ stored at index 0 — butlefthas already moved past it. Since0 < left, we don’t jump. Without the>= leftguard this would incorrectly shrink the window.”
“One character, one iteration. The map stores it, max is set to 1, and we return 1. No special casing needed.”
Step 5: State Time and Space Complexity
| Dimension | Complexity | Reason |
|---|---|---|
| Time | O(n) | right visits each character exactly once; left only ever moves forward — total work is O(n) |
| Space | O(min(n, m)) | The HashMap stores at most one entry per unique character — bounded by both the string length n and the character set size m (e.g., 128 for ASCII) |
“Time is O(n) —rightonly ever moves right, andleftonly ever moves right. Neither pointer ever backtracks, so every character is touched at most twice total. Space is O(min(n, m)) where m is the size of the character set. For ASCII it’s at most 128 entries — effectively O(1). For arbitrary Unicode it’s O(n) in the worst case.”
Interview Checklist for Longest Substring Without Repeating Characters
- Restate — “longest contiguous substring with no duplicate characters” — stress contiguous vs subsequence
- Mention brute force O(n³) → explain why it’s rejected
- Propose sliding window — define left and right pointers clearly
- Explain why HashMap (storing indices) beats HashSet (storing presence only)
- State the key insight — jump
leftdirectly past the duplicate’s last known index - Explain the
char_index[char] >= leftguard — critical for stale entries - Always update the map entry for
s[right]regardless of whether a jump occurred - Call out: empty string → 0, all-same → 1, all-unique → n
- Call out: duplicate outside window — must not move
leftbackward - State O(n) time and O(min(n, m)) space — mention the ASCII constant-space case
Related Problems to Practice
If you can solve this cleanly, try these next:
