On this pageToggle
Pattern: kill the inner loop
The N+1 of interviews
A Pune billing job compared every invoice with every other invoice to find a matching settlement. Two loops. Fine at 2,000 rows. At 2 lakh rows the batch window died.
I have seen the same shape in code reviews for ten years. The inner loop is a search. A search you could have replaced with a lookup.
In plain words: a hash map (dictionary) or hash set remembers what you already walked past, so you do not walk the list again.
When you finish this lesson you should be able to read n and know whether nested loops will time out. You should hear "find a pair," "have I seen this," or "count this" and reach for the right map. You should say the six-step interview script out loud before you type.
The problems after this (Contains Duplicate, Two Sum, anagrams) are homework on this one idea. Do not skip this page to "just see the code." The code is the last twenty percent.
Tradeoff: you pay RAM to buy time. That is the whole family.
Why Should You Care?
Days 3 and 4 of the 90-day plan are this family. Day 3 is Contains Duplicate and a stock scan. Day 4 is Two Sum, Valid Anagram, and Group Anagrams. If this page is mushy, every later hash problem feels new.
At work this is the same idea as a Redis cache. You look it up, or you compute it and remember it. You just never named it on an array. The Pune billing batch was not a clever algorithm problem. It was an inner loop that should have been a lookup.
In interviews, product companies grade process. If you only recite the map solution, they ask what else you considered, and you freeze. They want brute force said out loud, then killed. The map is step 4, not step 1.
🟢 The Simple Version (start here)
Think of an attendance register or a quick checklist in memory: When a new item arrives, you do not scan the entire notebook from top to bottom. You check that exact entry in O(1) time:
- If the item is already marked, you know immediately it is a duplicate.
- If not, you record it and continue.
Looking up an entry in a hash set or hash map takes O(1) average time. Comparing against every previous element with a nested loop takes O(n) per item, which grows to O(n²) for the entire array.
Hash set: "have I seen this value?" Only membership.
Hash map: "I have seen this value, and I stored something with it" (index, count, a list of words).
The inner loop in brute force is almost always "search for the partner." The set or map is that search with the walking removed.
Brute force After the map
for each A: for each A:
for each B: kill this → look up partner in the map
if A and B match if found, done
else store A
The one question I want you to ask on every array problem:
Can I replace a nested search with a lookup?
If yes, this is the pattern.
Recognition signals
| Phrase in the prompt | What you build | First problem |
|---|---|---|
| Find a pair that sums / differs | Map: value → index | Two Sum |
| Contains / seen before / duplicate | Set of values | Contains Duplicate |
| Count occurrences | Map: value → count | Valid Anagram |
| Group by some property | Map: signature → list | Group Anagrams |
| First unique / first non-repeating | Map: value → count, then a second pass | later week |
If the array is already sorted and they want indices of a pair, do not reach for a map first. That is Two Sum II. Different family. Two pointers. We teach that after these problems.
If the phrase fits a map, pause anyway. Some prompts look like lookup and are not.
Wrong family: when a hash is the wrong first tool
Sorted Two Sum II looks like Two Sum. The prompt still says "two numbers that add to target." The difference is the array is already sorted, and they often want O(1) extra memory. A map still finds the pair. They then ask why you spent O(n) RAM when two indices walking inward would have done it. The sort is the hint. Use it.
Walk [2, 7, 11, 15], target 9, both ways so you feel it. The map stores 2 → 0, then at 7 it looks up 2 and returns. Fine, and extra RAM. Opposite ends: 2 + 15 = 17 (too big, drop 15), 2 + 11 = 13 (too big, drop 11), 2 + 7 = 9. Same pair, two integers of extra memory. That is the family they wanted.
A contiguous slice looks like "have I seen this." Longest substring without repeating characters on abca is the trap. A set of the whole string sees a twice and wants to say "duplicate exists." That is Contains Duplicate. The actual question is the longest contiguous slice with unique letters. abc is legal (length 3). After the second a arrives, you drop the first a and keep bca (still 3). The set is a running fact for the current slice, and you throw the left end away when the slice breaks. That is a sliding window. Membership of the whole array is the wrong question.
UPI makes the same split at work. "Have we already processed this txnId?" is a set (or a unique index). "How many callbacks in the last 60 seconds?" is a window. If you dump every callback id into a set and call it a rate limit, you answered a different question.
The reverse mistake is also common, so I will name it. Unsorted [3, 2, 4], target 6, they want original indices. Sit i on 3 and j on 4. Sum 7, too big, so a Two Sum II loop moves j and never looks at 2 + 4. The proof that you can throw an end away needs sorted order. You do not have it. Look up target - x in a map. That is this week's tool.
Do not sort "to make two pointers legal" if they asked for original indices. Sort shuffles the positions unless you stored them first. Then you have built a slower, fussier map.
How big-O actually talks
In plain words: Big-O is the shape of the work as n grows. It is not milliseconds on your laptop.
Library with n books:
| Task | Shape | Name |
|---|---|---|
| Librarian already knows the shelf | same work at any n | O(1) |
| Books sorted, you keep halving | ~20 steps at a million | O(log n) |
| You scan every book once | grows with n | O(n) |
| You sort the shelf | n times a log | O(n log n) |
| You compare every book with every other | nested scan | O(n²) |
Interview time limits are roughly 1 to 2 seconds. Nested loops on n = 10⁵ are about 10¹⁰ visits. Dead. Nested loops on n = 10³ might pass and still get a "can you do better?"
Three counting rules
- Drop constants.
O(3n)isO(n). - Drop the smaller term.
O(n² + n)isO(n²). - Quote the worst case unless they ask average.
Space is extra memory, not the input array. A set of n keys is O(n) extra. Two integers is O(1) extra.
Backend picture, because you already live here:
| DSA | Your job |
|---|---|
| Hash map | Redis / Dictionary cache |
| Hash set | Dedup set, "have we processed this webhook id" |
| O(n²) nested scan | N+1 queries |
| Store then lookup | Look up, or compute and remember |
You precompute so you do not recompute. That is the pattern.
Constraint cheat table (say this in the room)
Read 1 <= n <= ... before you pick a technique.
| n up to | You need | What is allowed |
|---|---|---|
| 20 | even O(2ⁿ) | backtracking |
| 1,000 | O(n²) can live | nested pairs |
| 10⁵ | O(n) or O(n log n) | hash, sort, two pointers |
| 10⁶ | O(n) | one pass, hash, math |
If the prompt says n = 10⁵ and you start two nested loops, you have already told them you did not read constraints.
n lookups is O(n), not O(1). Each lookup is O(1) average. The loop still walks n items.
The 6-step interview script
Product companies grade this more than clever syntax. Use it on every problem in this path.
- Restate and clarify (2 min). Own words. Ask: duplicates? negatives? empty? return indices or values? sorted?
- Read n. Say the target complexity out loud.
- Brute force first (2 min). Describe the dumb correct idea and its Big-O. You do not have to type it. They need to hear it.
- Optimize. Ask "can I kill the inner loop?" Apply the map or the set.
- Code and narrate. Names that say what they hold (
need,seen, notx2). - Dry-run and complexity. Walk one sample. State time and extra memory.
If you skip step 3, a mid-level interviewer thinks you memorised NeetCode. If you skip step 1, you return values when they wanted indices.
Here is the same script on Two Sum, the one they will actually ask.
Restate. Two different indices whose values add to target. Classic prompt: exactly one pair.
Clarify. Duplicates allowed ([3,3] target 6). Negatives allowed. Return indices. Not sorted.
n. Often 10⁴. O(n²) is borderline. O(n) is the bar.
Brute. For i, for j > i, if sum matches, return. O(n²) time, O(1) extra.
Optimize. The inner loop searches for target - nums[i]. Store earlier values in a map. Look up, then store.
Time O(n), extra O(n). You bought the linear scan with a map of up to n keys.
Dry-run: Two Sum on [2, 11, 15, 7], target 9
I moved 7 to the end on purpose. The usual sample [2, 7, 11, 15] hits on the second index and hides the loop. Walk this one on paper. Look up before you insert.
| i | value | need (9 - value) |
map before | action |
|---|---|---|---|---|
| 0 | 2 | 7 | empty | 7 is missing. Store 2 → 0. |
| 1 | 11 | -2 | {2: 0} |
-2 is missing. Store 11 → 1. |
| 2 | 15 | -6 | {2: 0, 11: 1} |
-6 is missing. Store 15 → 2. |
| 3 | 7 | 2 | {2: 0, 11: 1, 15: 2} |
2 is at index 0. Return [0, 3]. |
You never nested a second loop. At i = 3 the partner was already sitting in the map from i = 0.
Now the trap. [3, 3], target 6. If you insert then look up, the first 3 lands in the map, the lookup finds the same index, and you return [0, 0]. The prompt said two different indices. Look up need first. Miss. Store. Next 3 looks up 3, finds index 0, returns [0, 1].
The usual sample [2, 7, 11, 15], target 9, is the same story in two rows. i = 0 stores 2 → 0. i = 1 needs 2, hits, returns [0, 1]. You still walk it in the room so they see you can.
Dry-run: Contains Duplicate on [1, 2, 3, 1]
Same spike, no partner, only membership.
| i | value | set before | action |
|---|---|---|---|
| 0 | 1 | empty | not seen, add 1 |
| 1 | 2 | {1} |
not seen, add 2 |
| 2 | 3 | {1, 2} |
not seen, add 3 |
| 3 | 1 | {1, 2, 3} |
1 is already there. Return true. |
If the last 1 had been a 4, the set would hold four keys and you would return false after the loop. That is the whole set shape.
Notice what you did not do. You did not sort. Sorting also finds a duplicate (equal neighbours), but it is O(n log n) and it shuffles indices if a later problem needs them. For "does any duplicate exist," the set is the honest first tool at n = 10⁵.
Three map shapes you will reuse
Complement map (Two Sum family): key is the value you saw, value is the index. Look up target - x before you insert x, so you never pair an element with itself.
Membership set (Contains Duplicate): only the keys. If insert fails (already there), you found a duplicate.
Frequency or group map: count letters, or bucket words by a signature. Valid Anagram and Group Anagrams.
I will not dump four languages here. Each problem does that after you have thought.
🔴 Architect's Corner
At India scale you do not load 10 crore ids into a process set to detect duplicates. That is a unique index, or a Bloom filter if you can live with false positives.
The interview still wants the in-memory set. Say both sentences: "For this n, a HashSet. In a KYC table with crores of PANs, I would not do this in the app."
Hash collisions exist. In the room, treat lookup as O(1) average unless they are fishing for that.
A map is a tool for n that fits in memory. The Pune job at 2 lakh invoices fits. A day of UPI txnIds across India does not. Do not let the interview answer become the production design by accident.
Common Mistakes
The belief is that starting with the optimal map looks senior. They then ask brute force and you blank. Process is the grade. Say the nested loop, then kill it.
The belief is that "hash is O(1), so the whole algorithm is O(1)." n lookups is O(n). Each glance is cheap. You still glance n times.
The belief is that sorting first is always safer. Sort shuffles indices. If they asked for original positions, you now have to store them, and you have paid O(n log n) for a problem a map solves in O(n).
The belief is that insert-then-lookup is the same as lookup-then-insert. On [3, 3] target 6 you match an index with itself. Look up the partner first.
The belief is that a map is the answer to every pair problem. Sorted Two Sum II wants two pointers. A contiguous budget wants a window. The map still appears inside a window as a running count. The family is not "hash the whole array."
The belief is that a set of everything you have ever seen answers "longest unique slice." That is Contains Duplicate thinking on a window prompt. Drop the left end, or you are solving the wrong question.
🧠 Key Takeaways
The inner loop is a search. A map is that search without the walk. You read n before you pick a technique. You say brute force out loud, then you kill it. You look up, then you store. You are buying time with RAM, and that is the tradeoff you say in the room. If they already sorted the array, or if they asked for a contiguous slice, you give that RAM back and pick the other family.
Think About It
- n = 10⁵, you need a pair that sums to target. Why is "I will sort, then two pointers" a different problem if they want original indices?
- A webhook handler must reject a duplicate
paymentIdit saw in the last 24 hours. Is the interview HashSet still the production answer? - Why does "exactly one solution" change what you return when the loop ends?
Pause here. Next lesson is Contains Duplicate. Open it only after you can say the one question without looking.
Full quizzes, answers, progress, case studies and interview problems available in the paid path.