Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Return true if s2 contains a substring that is a permutation (anagram) of s1. Since any permutation of s1 has the same length and character counts, slide a fixed-size window of length |s1| across s2 and check whether the window's character counts match s1's. Maintain the counts incrementally with a 'matches' counter. O(|s2|) time, O(1) space (26 counts).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognize that a permutation is just an equal-multiset, equal-length match, and use a fixed-size sliding window of counts updated incrementally rather than re-sorting or recounting each window.
Where candidates slip
Re-sorting or rebuilding the count map for every window (O(|s1|) per slide), or forgetting the add-and-remove pairing so the window drifts off its fixed size.
How to stand out
State the sort-each-window brute force, then 'counts must match and lengths are equal, so a fixed window of frequencies sliding in O(1) per step works'. Mention the matches counter for strict O(|s2|).
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewDoes s2 contain a permutation of s1?Is the window size fixed?Yes (= |s1|)Yes + incrementalExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Recount Every Window
1Hint 1Explore hintHide hint
A permutation of s1 has the same length as s1. So only substrings of s2 of length |s1| can match.
2Hint 2Explore hintHide hint
For each such window, count its characters (or sort it) and compare to s1's counts (or sorted s1).
3Hint 3Explore hintHide hint
Scan all windows and return true on the first match. Each window recount is O(|s1|) — that's the inefficiency to remove.
A · Core idea
Build the frequency count of s1. Then for every start index i in s2 such that a length-|s1| window fits, count the characters of s2[i .. i+|s1|-1] and compare to s1's counts. Return true on the first window whose counts match.
Why this is the natural first attempt. It hinges on the key realization: a permutation is exactly an equal-length, equal-frequency match. Once you see that, comparing counts per window is the obvious correct method (sorting each window and comparing strings is an equivalent, slightly slower variant).
Why we don't ship this. Recounting each window from scratch ignores that consecutive windows overlap in all but two characters. With |s1| and |s2| up to 10⁴, the O(|s2|·|s1|) product is wasteful. Sliding the window and updating only the entering and leaving character makes each step O(1).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s1 = "ab", s2 = "eidba""ei"
{e:1,i:1} ≠ need
notrueC · Code with teaching comments
Reference implementation previewpublic boolean checkInclusion(String s1, String s2) { int len = s1.length(); if (len > s2.length()) return false; int[] need = new int[26]; for (char c : s1.toCharArray()) need[c - 'a']++;Explore the full codeHide the full code
D · Why it works and complexityO(|s2| · |s1|) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Adjacent windows differ by exactly one character added on the right and one dropped on the left. Recounting the whole window throws that away; incremental count maintenance is the entire optimization.
ComplexityO(|s2| · |s1|) time, O(1) space — There are O(|s2|) windows; counting each from scratch is O(|s1|), giving O(|s2|·|s1|). The count arrays are fixed 26 ints → O(1) space.
E · Edge cases and pitfalls3Recounting each window instead of sliding — the O(|s1|) per-step cost.Explore edge casesHide edge cases
Pitfalls
- Recounting each window instead of sliding — the O(|s1|) per-step cost.
- Comparing as sorted strings repeatedly, which is even slower (O(|s1| log |s1|) per window).
- Missing the |s1| > |s2| early exit, risking out-of-range windows.
Approach 2 of 2 · Optimal
Fixed-Size Sliding Window of Counts (Optimal)
1Hint 1Explore hintHide hint
Both the target length (|s1|) and the comparison (equal counts) are fixed. Build s1's count array and an initial window of the first |s1| chars of s2.
2Hint 2Explore hintHide hint
Slide the window one step at a time: add the new right character's count and subtract the character that exits on the left. The window size stays |s1|.
3Hint 3Explore hintHide hint
To avoid re-comparing all 26 counts each slide, track a `matches` counter of how many of the 26 letters currently have equal counts in window and need; the window is a permutation when matches == 26.
A · Core idea
Because every permutation of s1 has length |s1|, the window size is fixed — we only ever look at length-|s1| slices of s2. Build need (s1's counts) and window (counts of the first |s1| characters of s2), then slide.
Sliding in O(1) per step. Moving the window one position right means exactly two changes: the new character at index i enters (increment its count) and the character at index i − |s1| leaves (decrement its count). The window's length is preserved automatically by always pairing one add with one remove. No recounting.
Checking the match cheaply with a `matches` counter. Comparing the two 26-length arrays every slide is O(26) — fine, and still O(|s2|) overall, but we can do strict O(1) per step. Keep matches = the number of letters x for which window[x] == need[x]. When we change a letter's window count, we look at whether that letter's equality flipped: if it was equal and now isn't, matches--; if it just became equal, matches++. The window is a permutation of s1 exactly when matches == 26.
Why equal counts is the whole test. Two strings are permutations of each other iff they have identical character frequencies. Length equality is guaranteed by the fixed window, so frequency equality is both necessary and sufficient — no need to consider order at all.
Why the early exit matters. If |s1| > |s2|, no window fits and the answer is immediately false; this also prevents constructing an out-of-range initial window.
Why O(|s2|) time and O(1) space. Each character of s2 enters and leaves the window at most once, and the only storage is two constant-size count arrays plus the matches counter.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s1 = "ab", s2 = "eidbaooo"Array state
i=0..1
window "ei" — counts ≠ need
trueC · Code with teaching comments
D · Why it works and complexityO(|s2|) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Fixing the window size collapses the problem to 'do these two frequency vectors match as the window slides?'. The add-one/remove-one update keeps each step O(1), and the matches counter turns the 26-way comparison into a single integer test — the same incremental-equality trick that solves Find All Anagrams.
ComplexityO(|s2|) time, O(1) space — Building the counts is O(|s1|). Sliding across s2 is O(|s2|) steps, each O(1) with the matches counter (or O(26) with array compare, still O(|s2|)). The two 26-slot arrays are O(1) space.
E · Edge cases and pitfalls6s1 = "a", s2 = "a" — Window of size 1 equals need immediately → returns true before any slide.Explore edge casesHide edge cases
Edge cases handled
s1 = "a", s2 = "a"Window of size 1 equals need immediately → returns true before any slide.s1 = "ab", s2 = "a"|s1| > |s2| → early return false.s1 = "abc", s2 = "ccccbbbbaaaa"No length-3 window mixes a,b,c equally → returns false.
Pitfalls
- Forgetting to check matches/equality for the initial window before sliding (missing a match at index 0).
- Updating matches with wrong threshold logic (the need[x] ± 1 boundary conditions).
- Not pairing every add with a remove, letting the window size drift away from |s1|.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic boolean checkInclusion(String s1, String s2) { int len = s1.length(); if (len > s2.length()) return false; int[] need = new int[26]; int[] window = new int[26];Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Rebuilding the window each step
Adjacent windows overlap in all but two characters; recounting from scratch is O(|s1|) per step and defeats the point of a sliding window.
Explore the code comparisonHide the code comparison
Rebuilding the window each step
Adjacent windows overlap in all but two characters; recounting from scratch is O(|s1|) per step and defeats the point of a sliding window.
Don't write this
for i in range(len(s2) - length + 1):
win = [0] * 26
for j in range(i, i + length):
win[ord(s2[j]) - ord('a')] += 1Write this instead
window[ord(s2[i]) - ord('a')] += 1
window[ord(s2[i - length]) - ord('a')] -= 12Comparing strings instead of counts
A permutation is defined by equal frequencies, not order. Sorting each window to compare is correct but slower than count comparison.
Explore the code comparisonHide the code comparison
Comparing strings instead of counts
A permutation is defined by equal frequencies, not order. Sorting each window to compare is correct but slower than count comparison.
Don't write this
char[] a = window.toCharArray();
Arrays.sort(a);
if (new String(a).equals(sortedS1)) return true;Write this instead
if (Arrays.equals(window, need)) return true;Revise
Carry the pattern into the interview
Pattern to remember
Fixed-size sliding window of frequency counts
Code pattern to memorize
if len(s1) > len(s2): return False
need, win = [0]*26, [0]*26
for c in s1: need[c]++
for i in range(len(s1)): win[s2[i]]++
if win == need: return True
for i in range(len(s1), len(s2)):
win[s2[i]] += 1
win[s2[i-len(s1)]] -= 1
if win == need: return True
return FalseRules to remember
- 1A permutation has the same length and same character counts — order is irrelevant.
- 2The window is fixed-size at |s1|; slide by adding the new char and removing the one that falls off.
- 3Compare counts, not strings — a 26-length count array comparison is O(1).
- 4Early-exit
falseif |s1| > |s2| (no window can fit). - 5A
matches/requiredcounter avoids re-comparing all 26 slots each slide for true O(|s2|).
Use this pattern when
- Detect an anagram/permutation as a contiguous substring
- The target window size is known and fixed
- Equality of multisets can be tracked incrementally as the window slides
- Fixed-length 'does this pattern appear (reordered)' substring problems
Choose another pattern when
- Window size varies with a constraint → variable sliding window (LC3, LC76)
- t must appear in order → substring search (KMP), not anagram matching
- Need all matching positions → same window, collect indices (LC438)
Interview takeaway
When the target length is fixed, a fixed-size sliding window of character counts answers anagram-presence in one pass. Add-one / remove-one count maintenance plus a matches counter is the template — it extends directly to Find All Anagrams.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternFixed-size sliding window of frequency counts with incremental add/remove and a matches counter. Reach for it on anagram/permutation-presence problems where the window length is known. Same family as Find All Anagrams and Valid Anagram.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Find All Anagrams in a StringComing soonSame fixed-size window, but collect every start index where counts match instead of returning on the first.
- Minimum Window SubstringVariable window that must cover (not equal) t — grow then shrink.
- Valid AnagramThe count-equality test without the window — just compare frequencies of two strings.
- Longest Substring Without Repeating CharactersVariable window with a distinctness constraint instead of a fixed-size count match.