intermediate7 min read·Updated September 19, 2026

Longest Substring Without Repeating Characters Explained: pwwkew & abba

Master the sliding window pattern for the longest substring without repeating characters. Follow a step-by-step trace of pwwkew and the tricky abba backward trap.

By Learnisim AI·Published September 19, 2026
LEARNING ARCSetup → Model in the example → Full trace → Twist → Edge cases → Apply
PREREQUISITES
  • short strings
Longest Substring Without Repeating Characters ("pwwkew" & "abba") Trace 1: s = "pwwkew" (Sliding Window & Last-Seen Index Map) Max Len: 3 0 1 2 3 4 5 p w w k e w left (3) right (5) Key Mechanism at right = 5 ('w'): 1. Character 'w' is already in map at index 2. 2. Since index (2) >= left (3 is old), left jumps to max(left, idx + 1) = 3. 3. Active window becomes "kew" (length 3). Map State: {'p':0, 'w':5, 'k':3, 'e':4} Trace 2: s = "abba" (The Backward-Left Trap & Guard Check) Max Len: 2 0 1 2 3 a b b a left (2) right (3) Crucial Guard Check at right = 3 ('a'): 1. Character 'a' is found in map at index 0. 2. GUARD TEST: Is map['a'] (0) >= left (2)? Result: NO! (0 < 2). Index 0 is outside our current active window. 3. Action: IGNORE map index. Do NOT move left pointer backward! 4. Window remains valid: "ba" (length 2). Prevents corrupting our window. Final Result: Valid longest substring length is 2.
Longest unique substring of "pwwkew" and of "abba" overview diagram
Why

Why We Need a Smarter Way to Scan Strings

Phase 1: The Scanning Trap

Imagine you are given the string and asked to find the longest contiguous section of characters where no letter repeats. If you try the most obvious brute-force approach, you would list every possible substring, check each one for duplicates, and pick the longest. For short strings that sounds harmless, but as the length grows, checking every pair of start and end indices explodes into or even operations.
Now consider a slightly more devious input, . When scanning from left to right, your intuition might tell you to just start over or jump backward whenever you hit a duplicate character. But as we will see later, naive backward jumps cause you to miscalculate overlapping character windows and ruin your result.
We need a way to slide across the string in a single forward pass, dynamically adjusting our window boundaries without re-scanning characters we have already validated.
Why We Need a Smarter Way to Scan Strings The Scanning Trap: Brute-force O(n³) checks vs. overlapping window pitfalls in "pwwkew" & "abba" Phase 1: Brute-Force Explosion O(n³) Input String s = "pwwkew" — checking every start & end pair 0 1 2 3 4 5 p w w k e w Re-checks duplicate pairs O(n²) / O(n³) • Explores all N(N+1)/2 substrings • Repetitive scans waste CPU cycles on overlapping states Phase 2: Naive Backward Jump Pitfall Input String s = "abba" — hitting duplicate 'a' at end 0 1 2 3 a b b a Jump left on 'a' ruins window! Misses max len = 2 • Naive jumps backward cause incorrect window shrinking • Fails to account for previously cleared indices Phase 3: The Smarter Way — Linear Sliding Window with Hash Map O(n) Track last seen character indices to instantly slide left boundary forward without backward movement. 1. Expand Right Pointer Scan characters one by one from left to right in single pass. 2. Check Hash Map If duplicate is found inside current window, retrieve its last index. 3. Jump Left Pointer Instantly set left = max(left, last_index + 1) in O(1) time.
Why We Need a Smarter Way to Scan Strings diagram
Model

Sliding Window and Char-Map Model for String Scanning

Phase 2: The Sliding Window Model

To find the longest substring without repeating characters in strings like "pwwkew" and "abba", we stop restarting from scratch at every index. We keep one window that is unique right now, and we slide it forward.
The window has two moving edges:
- right walks the string once, from index 0 to . Each step tries to include .
- left only moves forward. When is a duplicate that still sits inside the window, we jump left to one past that character's last index.
A hash map stores each character's last seen index. On each right:
1. Look up , the last index of , if any.
2. If exists and , the duplicate is still in the window, so set . Never move left backward — that is the "abba" trap.
3. Record in the map.
4. Update .
On "pwwkew" the window grows to "wke" or "kew" (length 3). On "abba", after the two s, left is already at the second ; the final must not pull left back to 0, so the answer stays 2, not 3.
Sliding Window & Char-Map: Longest Substring Without Repeating Characters Concrete Example: Scanning string "pwwkew" with pointers L and R 1. String Array & Sliding Window Pointers p idx 0 w idx 1 w R pointer k idx 3 e idx 4 w idx 5 Active Window [L=1, R=2] (Collision on 'w') L 2. Char Index Map (Last Seen Positions) 'p' -> index 0 'w' -> index 2 'k' -> index 3 'e' -> index 4 Rule: L = max(L, map[char] + 1) 3. Core Mechanics & Invariants Expand Window (R) • Iterate R from 0 to n - 1 • Include s[R] into current window • Check if character exists in map Time Complexity: O(n) Single pass string scan Resolve Duplicate • If s[R] is in map & map[s[R]] >= L: L = map[s[R]] + 1 • Skips past previous duplicate Ensures window remains strictly free of duplicate characters. Record Maximum Length • length = R - L + 1 • max_len = max(max_len, length) • Update map[s[R]] = R Result for "pwwkew" -> 3 (Substring: "wke" or "kew")
Sliding Window and Char-Map Model for String Scanning diagram
Worked example

Tracing the Sliding Window Algorithm on pwwkew and abba

Phase 3: Step-by-Step Trace of the Sliding Window

To see how the left and right pointers interact with our character map, let's trace our two canonical test strings: s = "pwwkew" and s = "abba". We maintain a map storing the most recent index of each character, a left pointer starting at index 0, and a max_len accumulator.

Trace 1: s = "pwwkew"

- right = 0, char = p: map = {"p": 0}. left = 0. Window = "p" (len 1). max_len = 1.
- right = 1, char = w: map = {"p": 0, "w": 1}. left = 0. Window = "pw" (len 2). max_len = 2.
- right = 2, char = w: w is in map at index 1, and (). We jump left to . map = {"p": 0, "w": 2}. Window = "w" (len 1). max_len = 2.
- right = 3, char = k: map = {"p": 0, "w": 2, "k": 3}. left = 2. Window = "wk" (len 2). max_len = 2.
- right = 4, char = e: map = {"p": 0, "w": 2, "k": 3, "e": 4}. left = 2. Window = "wke" (len 3). max_len = 3.
- right = 5, char = w: w is in map at index 2, and (). We jump left to . map = {"p": 0, "w": 5, "k": 3, "e": 4}. Window = "kew" (len 3). max_len = 3.

Trace 2: s = "abba" (The Backward-Left Trap)

- right = 0, char = a: map = {"a": 0}, left = 0, max_len = 1.
- right = 1, char = b: map = {"a": 0, "b": 1}, left = 0, max_len = 2.
- right = 2, char = b: b is at index 1 (). left jumps to . map = {"a": 0, "b": 2}, left = 2. Window = "b" (len 1).
- right = 3, char = a: a is in the map at index 0. However, index 0 is strictly less than current left (). Therefore, we ignore it and do not move left backward. left remains 2. Window = "ba" (len 2). max_len = 2.
Result for "pwwkew" is 3, and for "abba" is 2.
Phase 3: Sliding Window Trace & The Backward-Left Trap Trace 1: s = "pwwkew" (Jump left when duplicate index >= left) p 0 w 1 w 2 (dup) k 3 e 4 w 5 (dup) Key Window Actions: • right=2 ('w'): seen at index 1 (>= left). Jump left to 1 + 1 = 2. Window = "w". • right=5 ('w'): seen at index 2 (>= left). Jump left to 2 + 1 = 3. Max Len = 3. Trace 2: s = "abba" (The Backward-Left Trap & Guard Condition) a map: 0 b map: 1 b left=2 a idx 0 (old) Why we check index >= left (The Guard Condition): • At right = 3 ('a'), map says 'a' was at index 0. • But current left pointer is at index 2! • Index 0 is strictly less than left (0 < 2). • IGNORE it! Do NOT move left backward. Window remains "ba".
Tracing the Sliding Window Algorithm on pwwkew and abba diagram
Practice

Predicting the Window Slide on a Tricky Repeating Sequence

Phase 4: Practice

Now that you have seen how the left pointer leaps forward to rather than incrementing by one, it is time to test your mental model. Consider the string which we analyzed in the previous phase. Imagine the scanning window has just processed the first two characters (indices 0 and 1) and then encountered the first at index 2.
Before you look at the trace or write code for it, reason through what happens when the right pointer reaches the final character at index 3. Recall the core danger we established: the character was already seen at index 0, but our left pointer was already shifted past index 0 when we processed the duplicate s.
Work through the following question to verify your understanding of how the character-index map prevents illegal backward slides.
cpp
string s = "abba";
// Assume right pointer is at index 3 (character 'a').
// What is the stored index of 'a' in your map, what is the current 'left' pointer value, 
// and why must 'left' NOT move backward to 0?
Apply

Transferring the Sliding Window to Similar Substring Constraints

Phase 5: Applying the Pattern to New Substring Constraints

Now that you have traced how the sliding window and character index map process both and , you can transfer this exact mechanism to neighboring string problems. The core engine we built relies on a single right pointer expanding the boundary, a left pointer that only moves forward when a duplicate is found at or to the right of , and an integer map storing the last seen indices. This pattern reappears whenever you need to maintain a valid window state under character constraints.
Suppose you encounter a modified problem: finding the longest substring that contains at most two distinct characters, or finding the longest substring with repeating characters allowed up to replacements. While those problems require minor adjustments—such as keeping a frequency count instead of a last-seen index—the bounding logic remains identical. When designing an algorithmic solution for these variations, always check if your window contraction logic safely prevents the left pointer from retreating, preserving the strict invariant we established with .
python
def lengthOfLongestSubstringKDistinct(s: str, k: int) -> int:
    # How would you adapt the character index or frequency map
    # to handle at most k distinct characters instead of all unique?
    pass

FAQ

Why does the algorithm fail on 'abba' if we move the left pointer backward?
When you encounter the second 'a', if you move the left pointer back to where the first 'a' was, it incorrectly includes two 'b's in the window. The left pointer must only move forward to max(current_left, last_seen_index + 1).
What is the time and space complexity of the sliding window approach?
The time complexity is O(n) because both the left and right pointers traverse the string at most once. The space complexity is O(min(n, m)), where m is the size of the character charset stored in the hash map.
When should I use a sliding window instead of nested loops?
Use a sliding window when dealing with contiguous subarrays or substrings where a constraint (like uniqueness or sum) can be maintained dynamically by expanding and contracting the window edges.

Keep learning