I thought solving the problem was the round. The last ten minutes turned out to be the part that counted.
I'd put close to four months into Amazon prep by the time my onsite landed. Two coding rounds went fine. The third started fine too.
I solved the initial problem in about twenty minutes, walked through the test cases, and felt the round was already mostly won.
Then the interviewer asked one follow up.
The constraint they changed was small, the kind of thing I'd have called "minor" if I'd been describing it on paper.
It exposed that I'd been preparing for the wrong half of the round, and I spent the next ten minutes recovering ground I should never have lost.
That round was a Bar Raiser round.
I didn't know it at the time (Amazon doesn't tell you), but the debrief came back with the kind of feedback only the Bar Raiser writes:
"Strong initial solution, struggled to adapt under constraint change, did not demonstrate the structural reasoning we look for."
The phrasing is almost word for word from how Bar Raisers grade.
The follow up after the working solution is the round.
- The Bar Raiser is one of the four or five interviewers on the loop, but you won't know which one. Treat every round as if it's them.
- Amazon's pattern coverage is wider than Google's or Meta's, so going deep on three families isn't enough.
- The grade often lives in the last ten minutes after your first working solution, not in the first solution itself.
- Adapting after a constraint flip is a specific skill that grinding more problems doesn't build.
- The fix is interleaved practice plus an explicit constraint flip rep on every problem.
Where my approach was breaking
My prep model was the standard FAANG one.
Pick the two or three pattern families that show up most for the company. Go deep on those. Time myself on company tagged problems. Repeat until recall got fast.
For Google that worked well enough.
Google leans heavily on predicate search, counting, and graph traversal. If you're solid on those three, the round usually lands in your zone. I'd interviewed at Google with that exact prep and it had been fine.
Amazon's pattern set is wider.
Across the problems Amazon actually tests, you'll see:
- counting
- fixed and variable sliding window
- prefix sum
- LRU-style design
- randomized set design
- plain binary search
- 2D and staircase binary search variants
- queue design
- backtracking
That's roughly eleven distinct families against Google's seven or eight.
The depth strategy that worked for Google had a worse hit rate at Amazon, and on the round that turned into the Bar Raiser slot, the problem landed in a family I'd given less time to.
The deeper issue was something the wider pattern coverage just made more visible.
Even on patterns I had practised, my fluency was template recall, not invariant construction.
As long as the problem looked enough like a textbook example, I produced the standard solution quickly.
The moment the constraint shifted, I tried to retrieve a different template instead of modifying the one I had.
The Bar Raiser format is built to expose that gap.
The follow up after the working solution is the round.
What I started doing differently
After the rejection landed, I went back through every coding round I'd done in mock interviews, including the ones I'd thought went well.
Two patterns showed up across roughly half of them:
- I'd produced a correct solution within the time budget, but I couldn't have explained which invariant the data structure I picked was maintaining. The hash map worked. I couldn't have said cleanly what would break if I'd used an array instead.
- When the mock interviewer changed a constraint at the end (which the better mocks always did), I either started from scratch or stalled. I almost never reached for the original solution and asked which part of it depended on the constraint that had moved.
Both were habits I'd built by drilling for speed against template problems.
Neither was a coding skill I lacked.
They were prep habits.
I shifted two things.
First, every problem I solved got a constraint flip rep.
Whatever the original constraint was, I changed one piece of it and reimplemented.
Second, I started reading my old solutions out loud as if explaining them to an interviewer, naming the invariant the structure maintained on every line.
The first felt like more work for the same problem count.
The second felt like wasted time.
Both turned out to be the gap.
Here's what changed in practice:
| What I used to do | What I started doing |
| Solve the problem, check the answer, move on | Solve the problem, then write down the constraint change that would invalidate the solution most |
| Drill problems within one pattern family per week | Rotate between three or four pattern families inside a single session |
| Recall the optimal solution before writing any code | Get a correct O(n²) working first, confirm edge cases, then improve |
| Treat the follow up question in mocks as a bonus | Treat the follow up as the main rep |
| Explain the solution by walking through the code | Explain the solution by naming the invariant first |
What that looked like on the next Word Search follow up
A few weeks later I ran a mock that landed on Word Search.
Standard backtracking on a grid:
Given a board of characters and a target word, return whether the word can be formed by a path of adjacent cells, with no cell reused.
The solution is a depth first search from each starting cell with a visited mark.
def exist(board, word):
rows, cols = len(board), len(board[0])
def dfs(r, c, idx):
if idx == len(word):
return True
if r < 0 or r >= rows or c < 0 or c >= cols:
return False
if board[r][c] != word[idx]:
return False
tmp = board[r][c]
board[r][c] = '#'
found = (
dfs(r+1, c, idx+1)
or dfs(r-1, c, idx+1)
or dfs(r, c+1, idx+1)
or dfs(r, c-1, idx+1)
)
board[r][c] = tmp
return found
for r in range(rows):
for c in range(cols):
if dfs(r, c, 0):
return True
return False
The invariant the in-place marking maintains:
At any point in the recursion, the cells along the current path are marked #, every other cell is restored.
Backtracking by restoring on the way out keeps the invariant true at depth zero, which is what makes:
“No cell reused along this path”
hold.
Then came the follow up.
Instead of a single word, you now have a list of k words to find on the same board.
Same constraint.
No cell reused along a single word's path.
The old me would've started writing:
for w in words:
exist(board, w)
taken the cost of:
O(k * rows * cols * 4^L)
and gone home.
The interviewer would've asked the obvious question:
“Can you do better than the naive loop?”
Which is exactly what had happened in the real round.
This time I asked a different question first:
Which part of the original solution depended on the constraint that just changed?
The DFS was doing the same prefix walk repeatedly for every word sharing a prefix.
So instead of changing the traversal:
Change what the traversal checks against.
Compose the DFS with a trie of all k words.
Every board walk now checks against all words simultaneously.
The invariant doesn't change.
The base case changes.
The grid traversal stays.
The constraint changed.
Not the structure.
def find_words(board, words):
root = build_trie(words)
rows, cols = len(board), len(board[0])
found = set()
def dfs(r, c, node):
ch = board[r][c]
if ch == '#' or ch not in node.children:
return
nxt = node.children[ch]
if nxt.word:
found.add(nxt.word)
nxt.word = None
board[r][c] = '#'
for dr, dc in ((1,0), (-1,0), (0,1), (0,-1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols:
dfs(nr, nc, nxt)
board[r][c] = ch
for r in range(rows):
for c in range(cols):
dfs(r, c, root)
return list(found)
I got there in roughly four minutes.
Not because I'd drilled Word Search II as a separate template.
Because I'd been practising one question repeatedly:
Which part of the original depended on the constraint that changed?
The change was in what the DFS checked.
Not in the DFS itself.
Why interleaving works for breadth
The other half of the gap was simply pattern coverage.
Three families deep wasn't enough for Amazon's surface area.
But I also couldn't realistically go equally deep on eleven families in the time I had left.
What I could do was rotate between them.
Research on interleaved practice consistently shows that mixing problem types improves your ability to identify which pattern applies to a new problem.
Mixed practice feels harder.
Blocked practice feels easier.
That's exactly why it works.
Blocked practice gives you:
“This week is sliding window.”
The Bar Raiser gives you:
“Figure out what this is.”
The added friction during practice was the same identification work the round required.
Practically, that meant:
A session of four problems became:
four different families
not:
four sliding window problems.
What I'd tell someone in the same spot
A few rules I wish I'd had four months earlier.
1. Get a working solution before chasing the optimal one
Twenty-five minutes thinking before writing leaves no room for the follow up.
A correct O(n²) solution that gets improved beats a half-written O(n) attempt every time.
2. Treat the follow up as the round
When the interviewer changes a constraint:
Ask which part of your solution depended on that constraint.
Modify only that part.
The instinct to redesign from zero is expensive.
3. Practise the constraint flip explicitly
Every problem gets a second pass with one constraint changed.
Implement the modified version.
That rep builds adaptation.
And adaptation is what the round grades.
If someone had explicitly taught me how to identify which part of a solution depends on a changing constraint, it would've shortened the loop considerably. The closest thing to the work I had to build for myself later was a pattern-identification lesson I eventually added inside Codeintuition.
I originally posted a longer version on my own blog with the per-pattern breakdown across all eleven families and how the rest of Amazon's interview loop factors into the Bar Raiser grade.
What’s a constraint flip from an interview that exposed a solution you thought you understood?