Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Find the smallest window in s that contains all characters of t (with multiplicity). The interview-grade answer is a grow-then-shrink sliding window: expand right until the window covers t, then shrink left as far as possible while it still covers t, recording the smallest valid window. Track coverage with a need-count map and a have/need formed counter. O(|s| + |t|) time, O(Σ) space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can run a two-phase (grow then shrink) sliding window with character multiplicity, using a have/need formed counter to check coverage in O(1) instead of comparing maps.
Where candidates slip
Comparing the whole window map to the need map each step (O(Σ) per step), mishandling multiplicity, or incrementing `have` every time a needed char appears instead of only when its count first meets the requirement.
How to stand out
State the brute force, then describe the grow-then-shrink window and the formed counter. Emphasize multiplicity ('aab' needs two a's) and that `have` ticks up only when a count exactly reaches the requirement.
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 previewSmallest window in s covering all of tTrack coverage incrementally?No (recheck all)Yes (have/need)Explore 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 — Check Every Substring
1Hint 1Explore hintHide hint
The answer is some substring of s. How do you check whether one substring covers all of t with multiplicity?
2Hint 2Explore hintHide hint
Build a need-count map of t. For a candidate substring, count its characters and verify each need is met.
3Hint 3Explore hintHide hint
Enumerate all O(m²) substrings, test coverage, and keep the shortest that passes. Correct but slow — the coverage recheck is the waste.
A · Core idea
Precompute need, a count of each character in t. Then for every start i and end j, count the characters in s[i..j] and check it contains at least need[c] of every required c. Among all substrings that pass, keep the shortest.
Why this is the natural first attempt. It encodes the definition literally and forces you to define 'covers t' precisely: every required character appears with at least its required multiplicity. That coverage predicate is reused in the optimal solution.
Why we don't ship this. Enumerating all substrings and rechecking coverage from scratch is wildly redundant — adjacent windows share almost all their characters. With m up to 10⁵, the quadratic (times alphabet) cost is far too slow. The optimal keeps a running window count and an O(1) coverage check, so it never recounts.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "ADOBEC", t = "ABC"i=0
extend until "ADOBEC" covers A,B,C
valid len 6"ADOBEC"C · Code with teaching comments
Reference implementation previewpublic String minWindow(String s, String t) { if (s.length() < t.length()) return ""; Map<Character, Integer> need = new HashMap<>(); for (char c : t.toCharArray()) need.merge(c, 1, Integer::sum); int bestLen = Integer.MAX_VALUE, bestStart = -1;Explore the full codeHide the full code
D · Why it works and complexityO(m² · Σ) timeO(Σ) spaceOpen the invariant and proofExplore the proofHide the proof
Every adjacent substring differs by only one character on each end, yet the brute force recounts the entire window. Maintaining incremental counts plus a 'how many requirements are currently met' counter removes all the recounting.
ComplexityO(m² · Σ) time, O(Σ) space — O(m²) substrings; each coverage check compares counts over the alphabet, O(Σ) (or O(window) to count). Total O(m²·Σ). The count maps are O(Σ) space.
E · Edge cases and pitfalls3Ignoring multiplicity — treating t as a set so "aa" passes with a single 'a'.Explore edge casesHide edge cases
Pitfalls
- Ignoring multiplicity — treating t as a set so "aa" passes with a single 'a'.
- Rechecking full coverage every step (O(Σ)) instead of tracking it incrementally.
- Forgetting the m < n early exit and the empty-string fallback.
Approach 2 of 2 · Optimal
Grow-Then-Shrink Window + Formed Counter (Optimal)
1Hint 1Explore hintHide hint
Build need = counts of t and required = number of distinct chars in t. Keep a window count map and a counter `have` of how many distinct requirements are currently fully met.
2Hint 2Explore hintHide hint
Expand right: add s[right] to the window; if that char's window count just reached its need, increment have.
3Hint 3Explore hintHide hint
While have == required (window is valid), record the window if it's the smallest, then shrink from the left: remove s[left], and if that drops a needed char below its requirement, decrement have. Advance left.
A · Core idea
Precompute need (the count of each character in t) and required (the number of distinct characters in t). Maintain a window count map and an integer have = how many distinct required characters are currently satisfied at full multiplicity. The window is valid exactly when have == required.
Phase 1 — grow to feasibility. Move right across s, incrementing window[c]. The instant a required character's window count reaches its needed count (window[c] == need[c]), one more requirement is met, so have += 1. We only bump have on the exact match — counting beyond the requirement doesn't satisfy a new requirement, so it must not increment have again.
Phase 2 — shrink to minimality. Once have == required, the window covers t. But it may be padded with junk on the left. So while it stays valid, record it if it's the smallest seen, then try to shrink: decrement window[s[left]] and advance left. If removing that character drops a needed character below its requirement (window[c] < need[c]), the window is no longer valid, so have -= 1 and the shrink loop stops. This finds the tightest window ending at the current right.
Why the formed counter beats comparing maps. Naively you'd compare the whole window map to need at each step — O(Σ) per step. The have/required counter compresses 'is the window valid?' into a single integer comparison, updated in O(1) as characters enter and leave. That's what turns the brute force's O(m²·Σ) into O(m + n).
Why multiplicity is handled correctly. Because have increments only when a count reaches need[c] and decrements only when it falls below need[c], a character that needs two copies contributes to have only while both copies are present. The window can't be declared valid with too few copies.
Why O(m + n) time. Each pointer sweeps s once; building need is O(n). Every character enters the window once (via right) and leaves at most once (via left).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "ADOBECODEBANC", t = "ABC"Array state
r=5
"ADOBEC" covers A,B,C (have=3)
"BANC"C · Code with teaching comments
D · Why it works and complexityO(m + n) timeO(Σ) spaceOpen the invariant and proofExplore the proofHide the proof
The two-phase rhythm — grow until feasible, then shrink while still feasible — is the universal minimum-window template. The reusable optimization is the have == required counter: rather than re-verifying the whole constraint, track how close you are to satisfying it and adjust by ±1 as elements cross the window boundary.
ComplexityO(m + n) time, O(Σ) space — `right` advances m times and `left` advances at most m times, each doing O(1) map work → O(m) for the scan plus O(n) to build need = O(m + n) time. The maps hold at most Σ distinct characters → O(Σ) space.
E · Edge cases and pitfalls6s = "a", t = "aa" — need{a:2}; window never reaches 2 a's, have stays 0, best never set → returns "".Explore edge casesHide edge cases
Edge cases handled
s = "a", t = "aa"need{a:2}; window never reaches 2 a's, have stays 0, best never set → returns "".s = "ab", t = "b"Window grows to include 'b', shrinks to just "b" (len 1).s = "aa", t = "aa"Needs two a's; the only valid window is the whole "aa".
Pitfalls
- Incrementing have on every needed char instead of only when the count first reaches the requirement.
- Comparing have against t.length() instead of the number of distinct required characters.
- Decrementing have when a non-required char leaves the window (it should only react to needed chars dropping below requirement).
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic String minWindow(String s, String t) { if (s.length() < t.length()) return ""; Map<Character, Integer> need = new HashMap<>(); for (char c : t.toCharArray()) need.merge(c, 1, Integer::sum); int required = need.size();Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Bumping `have` past the requirement
have must count satisfied distinct requirements. Incrementing it whenever a needed char appears (even beyond need[c]) over-counts and declares invalid windows valid.
Explore the code comparisonHide the code comparison
Bumping `have` past the requirement
have must count satisfied distinct requirements. Incrementing it whenever a needed char appears (even beyond need[c]) over-counts and declares invalid windows valid.
Don't write this
if c in need:
have += 1Write this instead
if c in need and window[c] == need[c]:
have += 12Comparing have to len(t)
required is the number of *distinct* characters in t, not its length. For t = "aab", required is 2, not 3.
Explore the code comparisonHide the code comparison
Comparing have to len(t)
required is the number of *distinct* characters in t, not its length. For t = "aab", required is 2, not 3.
Don't write this
while (have == t.length()) {
// shrink
}Write this instead
int required = need.size();
while (have == required) {
// shrink
}3Forgetting the empty-result fallback
If no window ever covers t, bestLen stays at its sentinel; returning a substring then throws or returns garbage.
Explore the code comparisonHide the code comparison
Forgetting the empty-result fallback
If no window ever covers t, bestLen stays at its sentinel; returning a substring then throws or returns garbage.
Don't write this
return s[best_start:best_start + best_len]Write this instead
return "" if best_len == float('inf') else s[best_start:best_start + best_len]Revise
Carry the pattern into the interview
Pattern to remember
Grow-then-shrink window with have/need counts
Code pattern to memorize
need = Counter(t); required = len(need)
have = 0; window = {}
best = (inf, 0, 0)
l = 0
for r, c in enumerate(s):
window[c] += 1
if c in need and window[c] == need[c]: have += 1
while have == required:
if r - l + 1 < best[0]: best = (r-l+1, l, r)
window[s[l]] -= 1
if s[l] in need and window[s[l]] < need[s[l]]: have -= 1
l += 1Rules to remember
- 1Match characters with multiplicity — if t = "aab", the window needs two a's.
- 2
havecounts how many distinct required characters are fully satisfied; valid whenhave == required. - 3Increment
haveonly when a count reaches exactlyneed[c](not above). - 4Shrink from the left while the window is valid, recording the minimum, then break validity by dropping a needed char.
- 5Return "" when no valid window exists (best length never updated).
Use this pattern when
- Smallest contiguous window that covers a required multiset of items
- Coverage can be tracked incrementally with a need map and a satisfied counter
- Grow to become feasible, then shrink to minimize — a two-phase window
- Substring/subarray 'contains all of X' minimization problems
Choose another pattern when
- Order of t matters / t must appear contiguously → that's substring search (KMP)
- Longest valid window, not shortest → grow-only window (LC3, LC424)
- Fixed-size window → simpler fixed sliding window (Permutation in String)
Interview takeaway
Grow-then-shrink is the template for minimum-window problems: expand the right edge until the constraint is met, then contract the left edge while it stays met. The have/need formed counter turns 'does the window cover t?' into an O(1) check per step.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternGrow-then-shrink sliding window with a have/required formed counter. Reach for it on 'smallest window covering a required multiset' problems. Same family as Permutation in String and Find All Anagrams.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Permutation in StringFixed-size window covering exactly t's multiset — no shrink phase needed.
- Find All Anagrams in a StringComing soonSame fixed window as Permutation in String, but collect every matching start index.
- Longest Substring Without Repeating CharactersGrow-only window — the longest-instead-of-shortest variable window.
- Minimum Window SubsequenceComing soont must appear in order as a subsequence — needs a two-pointer / DP twist, not just counts.