Few programming interview problems are as famous, as deceptively simple, and as useful for learning algorithmic thinking as “Longest Substring Without Repeating Characters.” At first glance, it sounds like a small string puzzle: given a string, find the length of the longest portion that contains no repeated characters. But beneath that simple wording is a powerful idea used in many real-world text processing, streaming, and optimization tasks: the sliding window.
TLDR: The problem asks you to find the longest continuous substring in which every character appears at most once. The best solution uses a sliding window and a data structure such as a set or hash map to track characters efficiently. Instead of checking every possible substring, the algorithm expands and shrinks a window as it scans the string once, achieving O(n) time complexity. This makes it a classic example of turning a brute-force problem into an elegant linear-time solution.
Understanding the Problem
The phrase “substring” is important. A substring is a continuous section of a string. For example, in the string "abcde", "bcd" is a substring, but "ace" is not, because the characters are not adjacent in the original string.
The goal is to find the longest substring that contains no duplicate characters. Consider this example:
"abcabcbb"
The valid substrings without repeating characters include "abc", "bca", "cab", and a few shorter ones. The longest length is 3, because no substring without repeats is longer than three characters.
Another example:
"bbbbb"
Here, the answer is 1, because every character is the same. The best we can do is select one "b".
And for:
"pwwkew"
The answer is 3, because "wke" is a substring without repeating characters. Notice that "pwke" is not valid because it is not continuous in the original string.
Why the Brute-Force Approach Is Inefficient
A natural first idea is to check every possible substring and test whether it contains repeated characters. This approach works, but it is slow.
For a string of length n, there are roughly n × n possible substrings. If you also spend time checking each substring for duplicates, the solution can become even more expensive. A naive implementation may reach O(n³) time complexity, while a slightly improved brute-force version might use O(n²).
That may be fine for a tiny string, but it becomes impractical for large input. If a string contains thousands or millions of characters, checking all substrings is unnecessary work. The algorithm keeps rediscovering information it already knows.
This is where the key insight appears: instead of generating every substring, we can maintain a moving window of valid characters.
The Sliding Window Idea
The sliding window technique is used when you need to examine a continuous range of elements in a sequence. In this problem, the window represents the current substring that contains no repeating characters.
Imagine two pointers:
- Left pointer: marks the beginning of the current substring.
- Right pointer: scans forward through the string, one character at a time.
As the right pointer moves, we add new characters to the current window. If the new character is not already inside the window, everything is fine. We update our maximum length if the current window is larger than any previous one.
But if the new character already exists in the current window, we have a problem: the substring now contains a duplicate. To fix it, we move the left pointer forward until the duplicate is removed.
This process is efficient because each character is added and removed at most once. The window grows and shrinks, but it never moves backward.
Walking Through an Example
Let’s trace the string:
"abcabcbb"
We start with an empty window.
- Read
a. The window is"a". Maximum length is 1. - Read
b. The window is"ab". Maximum length is 2. - Read
c. The window is"abc". Maximum length is 3. - Read
a. Nowais repeated. Move the left pointer past the firsta. The window becomes"bca". - Read
b. Duplicate again. Move the left pointer past the previousb. The window becomes"cab". - Read
c. Duplicate again. The window becomes"abc". - Read
b. Duplicate. Shrink the window until valid. - Read final
b. Again, adjust the window.
The longest valid window seen during the process is still "abc", with length 3.
Using a Set
One common way to implement the sliding window is with a set. A set stores the characters currently inside the window. Because set lookup is fast, we can quickly ask: Is this character already in the current substring?
Here is the general logic:
- Create an empty set to store current characters.
- Set both left and right pointers to the beginning of the string.
- Move the right pointer through the string.
- If the current character is not in the set, add it and update the maximum length.
- If the character is already in the set, remove characters from the left side until the duplicate is gone.
In JavaScript-like pseudocode, that looks like this:
function lengthOfLongestSubstring(s) {
let seen = new Set();
let left = 0;
let maxLength = 0;
for (let right = 0; right < s.length; right++) {
while (seen.has(s[right])) {
seen.delete(s[left]);
left++;
}
seen.add(s[right]);
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
This solution is clear and effective. The while loop removes characters from the left until the duplicate character can safely be added.
Using a Hash Map for an Even Cleaner Jump
There is another popular version that uses a hash map to store the most recent index of each character. Instead of moving the left pointer one step at a time, we can sometimes jump it directly past the previous occurrence of the duplicate.
For example, suppose we are scanning "abba". When we reach the second b, we know the previous b was at index 1. So the left pointer can jump to index 2. Later, when we see a, we must be careful: the previous a was at index 0, but that is already outside the current window. We should not move the left pointer backward.
That is why the update usually looks like this:
left = Math.max(left, lastSeen[char] + 1)
This ensures the left pointer only moves forward.
function lengthOfLongestSubstring(s) {
let lastSeen = new Map();
let left = 0;
let maxLength = 0;
for (let right = 0; right < s.length; right++) {
let char = s[right];
if (lastSeen.has(char)) {
left = Math.max(left, lastSeen.get(char) + 1);
}
lastSeen.set(char, right);
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
This version is compact and avoids repeatedly deleting characters from a set. Both approaches are valid, and both run in linear time.
Time and Space Complexity
The optimized sliding window solution has a time complexity of O(n), where n is the length of the string. This is because each character is processed as the right pointer moves across the string, and the left pointer also only moves forward.
The space complexity is usually O(k), where k is the number of possible distinct characters. If the string contains only lowercase English letters, then k is at most 26. If the input can contain Unicode characters, emojis, or symbols, the set or map may grow larger.
In practical terms, the memory use is tied to the number of unique characters that may appear in the current window or in the map of last-seen locations.
Common Edge Cases
A strong solution should handle tricky inputs gracefully. Some important cases include:
- Empty string:
""should return0. - Single character:
"a"should return1. - All repeated characters:
"aaaaa"should return1. - No repeated characters:
"abcdef"should return6. - Repeats outside the current window:
"abba"should return2, not3.
The "abba" case is especially useful because it exposes a common bug: moving the left pointer backward when using a hash map. Always ensure the left pointer remains at the maximum of its current position and the index after the repeated character.
Why This Problem Matters
This problem is not just an interview exercise. It teaches a reusable pattern. Sliding windows are useful whenever you need to analyze a continuous segment of data under certain constraints.
You will see similar techniques in problems such as:
- Finding the longest substring with at most two distinct characters.
- Finding the minimum window that contains all required characters.
- Calculating maximum sums of subarrays of fixed or variable length.
- Detecting patterns in streams of events or logs.
- Processing network packets, text input, or real-time sensor data.
The broader lesson is that algorithms often improve when you avoid recomputing from scratch. By preserving useful state as you move through the input, you can turn a slow nested-loop solution into a fast single-pass solution.
Set vs. Hash Map: Which Should You Use?
The set-based approach is often easier to understand. It directly represents the current valid window, making it great for learning and explaining the sliding window technique.
The hash map approach is slightly more advanced but can be more concise. It remembers the last position of every character and lets the left pointer jump forward when necessary.
In interviews, either is acceptable if implemented correctly. If you are new to the problem, start with the set version. Once you understand how the window expands and shrinks, study the hash map version to see how it optimizes pointer movement.
Final Thoughts
The Longest Substring Without Repeating Characters problem is a perfect example of how a simple question can reveal a deep algorithmic idea. The brute-force method asks, “What if we checked everything?” The sliding window method asks a better question: “What information can we keep as we move forward?”
By using two pointers and a set or hash map, we can solve the problem in O(n) time with clean, readable logic. More importantly, we gain a technique that applies far beyond strings. Once the sliding window pattern clicks, many other problems involving contiguous ranges become easier to recognize, reason about, and solve.
