Coding interview patterns are the small set of reusable shapes that most LeetCode-style questions secretly reduce to, and once you can name the shape you stop staring at a blank editor and start writing. This post walks through the six patterns that carry the most weight in real technical screens, two pointers, sliding window, fast and slow pointers, prefix sums, hashmap-first thinking, and dynamic programming, each implemented and run on Python 3.14.6 so you can watch them work instead of taking my word for it.
“Smart data structures and dumb code works a lot better than the other way around.”
Eric S. Raymond, The Cathedral and the Bazaar
Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Advanced | Reading Time: 25 minutes
Here is the everyday version. Say a cook named Aditi has a hundred recipes but really only five techniques: sauté, boil, roast, blend, ferment. Every new dish is just those techniques rearranged. Coding interviews work the same way. There are hundreds of problems on the practice sites, but a handful of underlying techniques solve the vast majority of them. Beginners try to memorize dishes. People who pass interviews learn the techniques and recognize which one a problem is asking for. That recognition, not raw cleverness, is the skill this post builds.
This is the pattern layer that sits on top of the algorithm basics: complexity, recursion, trees, and graphs. If you have those under your belt, what is left is learning to look at a problem statement and think “that phrasing means sliding window” before you have written a single line. Let’s build that instinct one pattern at a time, then map every pattern to the wording that triggers it.
The map above is the whole post in one picture: on the left, the kind of phrase you see in a problem, and on the right, the tool that phrase should make you reach for. Green tools are the cheap linear scans you want, blue are the ones that trade a little setup for fast lookups, and orange is dynamic programming, the heavyweight you save for problems with overlapping subproblems. Everything below is a walk through this map with running code attached.
Table of Contents
What Pattern Recognition Really Means
A pattern is a reusable strategy plus the clue that tells you to use it. The strategy is the code shape, like “walk two indexes toward each other.” The clue is a phrase in the problem, like “the array is sorted” or “find the longest substring.” Interviewers deliberately word questions so the clue is there if you know to look. Your job in the first thirty seconds is not to solve the problem, it is to classify it. Once you have the right pattern, the code almost writes itself, and just as importantly, you already know the time and space complexity before you type.
The payoff is huge. A brute-force answer to most of these problems is a nested loop, which is O(n squared) and usually times out on the largest hidden test case. Every pattern here exists to knock that down to O(n) or O(n log n) by being smarter about what you reuse instead of recomputing. Watch for that theme in every example: we never throw away work we already did.
Two Pointers
Picture two people reading a printed price list from opposite ends, one from the top and one from the bottom, calling out numbers and walking toward the middle until they meet. That is the two-pointer pattern. It shines on sorted arrays, because the sort tells you which pointer to move: if the current pair is too small, nudge the low end up; if it is too big, pull the high end down. You never need a nested loop.
📄 two_pointers.py: find a pair that sums to a target, and reverse in place
# Two pointers: find a pair that sums to target in a SORTED array
def pair_sum(nums, target):
lo, hi = 0, len(nums) - 1
while lo < hi:
s = nums[lo] + nums[hi]
if s == target:
return (lo, hi)
elif s < target: # need a bigger sum, move left pointer up
lo += 1
else: # need a smaller sum, move right pointer down
hi -= 1
return None
# Two pointers: reverse a list in place, no extra copy
def reverse_in_place(nums):
lo, hi = 0, len(nums) - 1
while lo < hi:
nums[lo], nums[hi] = nums[hi], nums[lo]
lo += 1
hi -= 1
return nums
prices = [1, 3, 4, 5, 7, 10, 11]
print("array:", prices)
for target in (15, 9, 100):
r = pair_sum(prices, target)
if r:
i, j = r
print(f"pair summing to {target}: {r} -> {prices[i]} + {prices[j]}")
else:
print(f"pair summing to {target}: None")
print("reversed:", reverse_in_place([10, 20, 30, 40, 50]))
▶ Output
array: [1, 3, 4, 5, 7, 10, 11] pair summing to 15: (2, 6) -> 4 + 11 pair summing to 9: (2, 3) -> 4 + 5 pair summing to 100: None reversed: [50, 40, 30, 20, 10]
What happened here: Both pointers start at the ends and only ever move inward, so together they touch each element at most once, giving O(n) time and O(1) extra space. For the target 15 the pair 4 and 11 is found immediately from the outside in; for 9 the high pointer walks down until the sum drops into range; for 100 the pointers cross without a match and the function returns None.
The reverse function is the same idea in miniature, swapping the outer pair and stepping inward, which is why reversing a list is linear and needs no second array. The clue that screams “two pointers” is a sorted input plus a request for a pair or a symmetric operation.
Sliding Window
Imagine looking at a train through a fixed gap in a fence. As the train moves, one carriage leaves the gap on the left exactly as a new one enters on the right, and you only ever look at the carriages inside the gap. A sliding window does that over an array or string: instead of recomputing a fresh sum for every position, you add the item entering the window and subtract the one leaving. That turns an O(n times k) brute force into a single O(n) pass.
📄 sliding_window.py: fixed window sum and longest unique substring
# Fixed window: largest sum of any k consecutive items
def max_window_sum(nums, k):
window = sum(nums[:k]) # first window, O(k) once
best = window
for i in range(k, len(nums)):
window += nums[i] - nums[i - k] # add new, drop old: O(1) per slide
best = max(best, window)
return best
# Variable window: longest run with no repeated character
def longest_unique(s):
seen = {} # char -> last index it appeared
start = 0
best = 0
for i, ch in enumerate(s):
if ch in seen and seen[ch] >= start:
start = seen[ch] + 1 # shrink window past the repeat
seen[ch] = i
best = max(best, i - start + 1)
return best
daily = [2, 1, 5, 1, 3, 2]
print("array:", daily, "k=3")
print("max sum of 3 in a row:", max_window_sum(daily, 3))
print()
for word in ["abcabcbb", "bbbbb", "pwwkew", "anvi"]:
print(f"longest unique run in {word!r}: {longest_unique(word)}")
▶ Output
array: [2, 1, 5, 1, 3, 2] k=3 max sum of 3 in a row: 9 longest unique run in 'abcabcbb': 3 longest unique run in 'bbbbb': 1 longest unique run in 'pwwkew': 3 longest unique run in 'anvi': 4
What happened here: The fixed window builds the first sum once, then each slide is a single add and a single subtract, so the whole scan is O(n) no matter how big k gets. The best three-in-a-row here is 5 plus 1 plus 3, which is 9. The variable-window version is the more common interview shape: it grows the window by moving the right edge forward and shrinks it by jumping start past any repeat, using a dictionary to remember where each character was last seen.
For “abcabcbb” the longest clean run is “abc” at length 3, for “bbbbb” it is a single “b”, and “anvi” has no repeats so the whole word counts. The clue for sliding window is the words “longest,” “shortest,” “maximum,” or “contains,” applied to a contiguous subarray or substring.
Fast and Slow Pointers
Two runners start together on a track. One jogs, the other sprints at double speed. If the track is a straight line, the sprinter simply reaches the end. If the track is a loop, the sprinter eventually laps the jogger and they meet again. That is the entire idea behind fast and slow pointers, and it answers two classic questions in one pass with only a couple of variables: does a linked list contain a cycle, and what is its middle node?
📄 fast_slow.py: cycle detection and midpoint in one pass
class Node:
def __init__(self, val, nxt=None):
self.val = val
self.next = nxt
def build(values):
head = None
for v in reversed(values):
head = Node(v, head)
return head
# Fast/slow pointers: does the linked list loop back on itself?
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next # one step
fast = fast.next.next # two steps
if slow is fast: # they meet only if there is a loop
return True
return False
# Fast/slow pointers: find the middle node in one pass
def middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow.val
chain = build([10, 20, 30, 40, 50])
print("straight list, has cycle:", has_cycle(chain))
print("middle of 5-node list: ", middle(chain))
# Make the tail point back to node 3 to create a loop
looped = build([1, 2, 3, 4])
looped.next.next.next.next = looped.next.next
print("looped list, has cycle: ", has_cycle(looped))
▶ Output
straight list, has cycle: False middle of 5-node list: 30 looped list, has cycle: True
What happened here: When the fast pointer moves two steps for every one the slow pointer takes, two things fall out for free. On a straight list the fast pointer runs off the end and the loop stops, so has_cycle returns False. When the slow pointer stops, it is sitting exactly halfway, which is why middle returns 30 for the five-node list. On the looped list, where the tail is wired back to the third node, the fast pointer keeps circling and eventually lands on the same node as the slow one, so slow is fast becomes true and we report a cycle.
All of this uses O(1) extra memory, which is the selling point: you detect a loop without storing every node you have seen. The clue is any linked-list problem that mentions a cycle, a midpoint, or “without extra space.”
Prefix Sums and Hashmap-First Thinking
These two patterns share a mindset: pay a little up front so every later question is cheap. A prefix sum is like a running bank balance. If you write down your total after each transaction, you can answer “how much did I spend between Tuesday and Friday” by subtracting two balances instead of re-adding every day. Hashmap-first thinking is the habit of asking “what have I already seen?” and storing it in a dictionary so the answer to that question is one O(1) lookup instead of a scan.
📄 prefix_hash.py: range sums in O(1) and two-sum in one pass
# Prefix sums: answer many range-sum questions in O(1) each after an O(n) build
def build_prefix(nums):
prefix = [0]
for x in nums:
prefix.append(prefix[-1] + x) # prefix[i] = sum of first i items
return prefix
def range_sum(prefix, lo, hi): # inclusive indices lo..hi
return prefix[hi + 1] - prefix[lo]
sales = [4, 2, 7, 1, 3, 9, 6]
prefix = build_prefix(sales)
print("array: ", sales)
print("prefix:", prefix)
print("sum of index 1..4:", range_sum(prefix, 1, 4), "(2+7+1+3)")
print("sum of index 0..6:", range_sum(prefix, 0, 6))
print("sum of index 3..5:", range_sum(prefix, 3, 5), "(1+3+9)")
print()
# Hashmap-first: classic two-sum on an UNSORTED array, returns indices, one pass
def two_sum(nums, target):
seen = {} # value -> index we saw it at
for i, x in enumerate(nums):
need = target - x
if need in seen: # O(1) lookup, no inner loop
return (seen[need], i)
seen[x] = i
return None
nums = [8, 3, 11, 7, 2]
print("array:", nums)
print("two-sum indices for 10:", two_sum(nums, 10), "->", "3 + 7")
print("two-sum indices for 19:", two_sum(nums, 19), "->", "8 + 11")
▶ Output
array: [4, 2, 7, 1, 3, 9, 6] prefix: [0, 4, 6, 13, 14, 17, 26, 32] sum of index 1..4: 13 (2+7+1+3) sum of index 0..6: 32 sum of index 3..5: 13 (1+3+9) array: [8, 3, 11, 7, 2] two-sum indices for 10: (1, 3) -> 3 + 7 two-sum indices for 19: (0, 2) -> 8 + 11
What happened here: The prefix array holds a running total, so prefix[i] is the sum of the first i items. Any range sum is then just the difference of two entries, which is why “sum of index 1 to 4” is prefix[5] - prefix[1], that is 17 minus 4, giving 13. Build it once in O(n), then every range query is O(1) forever. The two-sum function is the hashmap-first pattern in its purest form: for each number it computes what partner it needs, checks the dictionary of numbers already seen, and returns the moment it finds one.
That is a single O(n) pass, versus the O(n squared) nested loop most people write first. Notice this version does not need the array sorted, which is exactly when you pick a hashmap over two pointers.
Dynamic Programming: From Memo to Table
Dynamic programming sounds scary and is not. It is one idea: if a problem keeps asking the same smaller questions, answer each smaller question once and write the answer down. The classic demo is Fibonacci. The naive recursion recomputes fib(30) millions of times because both branches keep re-asking for the same values. Two fixes exist. Memoization (top-down) keeps the recursion but caches each answer. A table (bottom-up) throws away recursion entirely and fills an array from the smallest case up. Let’s time all three so the gap is undeniable.
📄 dp.py: fibonacci three ways, climbing stairs, and coin change
from functools import lru_cache
import time
# --- Fibonacci three ways: naive, memoized, bottom-up table ---
def fib_naive(n):
if n < 2:
return n
return fib_naive(n - 1) + fib_naive(n - 2) # recomputes the same values
@lru_cache(maxsize=None)
def fib_memo(n): # top-down: cache each answer once
if n < 2:
return n
return fib_memo(n - 1) + fib_memo(n - 2)
def fib_table(n): # bottom-up: fill an array left to right
if n < 2:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
t0 = time.perf_counter()
naive = fib_naive(32)
t1 = time.perf_counter()
memo = fib_memo(32)
t2 = time.perf_counter()
print(f"fib(32) naive = {naive} in {(t1-t0)*1e3:8.2f} ms")
print(f"fib(32) memo = {memo} in {(t2-t1)*1e6:8.2f} us")
print(f"fib(100) table = {fib_table(100)}")
print()
# --- Climbing stairs: how many ways to reach step n taking 1 or 2 at a time ---
def climb_stairs(n):
a, b = 1, 1 # ways to reach step 0 and step 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
print("ways to climb:", {n: climb_stairs(n) for n in range(1, 8)})
print()
# --- Coin change: fewest coins to make an amount (classic DP table) ---
def coin_change(coins, amount):
INF = amount + 1
dp = [0] + [INF] * amount # dp[x] = fewest coins to make x
for x in range(1, amount + 1):
for c in coins:
if c <= x:
dp[x] = min(dp[x], dp[x - c] + 1)
return dp[amount] if dp[amount] != INF else -1
print("coins [1,5,10,25], make 63:", coin_change([1, 5, 10, 25], 63))
print("coins [2], make 3 (impossible):", coin_change([2], 3))
print("coins [1,3,4], make 6:", coin_change([1, 3, 4], 6))
▶ Output
fib(32) naive = 2178309 in 412.79 ms
fib(32) memo = 2178309 in 60.30 us
fib(100) table = 354224848179261915075
ways to climb: {1: 1, 2: 2, 3: 3, 4: 5, 5: 8, 6: 13, 7: 21}
coins [1,5,10,25], make 63: 6
coins [2], make 3 (impossible): -1
coins [1,3,4], make 6: 2
What happened here: The naive Fibonacci took about 413 milliseconds for fib(32), while the memoized version got the identical answer in 60 microseconds, roughly seven thousand times faster, purely by not recomputing what it already knew. The table version scales even further and computes fib(100) instantly because it never recurses at all.
Climbing stairs is Fibonacci in disguise: the ways to reach step n equal the ways to reach n minus 1 plus the ways to reach n minus 2, which is why the counts are 1, 2, 3, 5, 8. Coin change is the step up to a real DP table, where dp[x] is the fewest coins to make amount x and each entry is built from smaller ones.
It finds 6 coins for 63 cents, correctly returns -1 when only 2-cent coins cannot make 3, and picks 2 coins (3 plus 3) for 6 with the set 1, 3, 4. The clue for DP is any problem asking to count the number of ways, or to find the fewest or the most of something, where the same subproblem shows up again and again.
The Pattern Cheat Sheet
This table is the one thing to burn into memory: the coding interview patterns above, plus two bonus tools, keyed by the wording that triggers them. When you read a problem, scan for the phrase on the left, and let it point you at the tool on the right before you write anything. This mapping is what separates candidates who freeze from candidates who start with a plan.
| When the problem says… | Reach for… | Typical cost |
|---|---|---|
| Sorted array, find a pair or triple | Two pointers | O(n) |
| Longest or shortest contiguous run | Sliding window | O(n) |
| Linked list cycle or midpoint | Fast and slow pointers | O(n), O(1) space |
| Many range-sum questions on one array | Prefix sums | O(1) per query |
| Find, count, or dedupe in one pass | Hashmap first | O(n) |
| Count ways, or fewest/most, overlapping subproblems | Dynamic programming | O(n) to O(n*m) |
| Explore a grid, tree, or graph fully | BFS or DFS | O(V + E) |
| Top k or streaming smallest/largest | Heap (heapq) | O(n log k) |
Ten Problems to Prove It
Reading about coding interview patterns is not the same as owning them. Here are ten well-known problems at the Easy and Medium level, each tagged with the pattern that cracks it and a one-line note on the approach. Try each one on a practice site before you peek at the note, and if you can name the pattern in the first minute, you are already doing the interview-day thing right.
| # | Problem | Pattern | Approach in one line |
|---|---|---|---|
| 1 | Two Sum | Hashmap first | Store each value, look up target minus value. |
| 2 | Valid Palindrome | Two pointers | Compare ends walking inward. |
| 3 | Best Time to Buy and Sell Stock | Sliding window | Track lowest price so far, best profit after it. |
| 4 | Longest Substring Without Repeating | Sliding window | Grow the window, jump start past repeats. |
| 5 | Linked List Cycle | Fast and slow | One-step and two-step pointers, check if they meet. |
| 6 | Subarray Sum Equals K | Prefix sums plus hashmap | Count prefix sums seen; look for current minus k. |
| 7 | Group Anagrams | Hashmap first | Key each word by its sorted letters. |
| 8 | Climbing Stairs | Dynamic programming | Ways(n) equals ways(n-1) plus ways(n-2). |
| 9 | Coin Change | Dynamic programming | Table of fewest coins per amount, build upward. |
| 10 | Number of Islands | DFS or BFS on a grid | Flood-fill each unvisited land cell, count launches. |
Six of these ten are solved by patterns you ran code for above, and the last one leans on the tree and graph traversal from earlier in this chapter. That overlap is the point: a small toolkit covers a big surface. Solve each of these, then find three more of each type on any practice site, and the shapes start to feel automatic.
How a Coding Round Actually Runs
The code is only half the score. The other half is how you work, and a lot of strong coders fail rounds because they go silent, dive straight into typing, and never say what they are thinking. Interviewers are grading your process as much as your answer. Here is the structure that reliably reads as “senior” in a 45-minute slot.
- Minutes 0 to 5, clarify. Restate the problem in your words. Ask about input size, empty inputs, duplicates, and whether the array is sorted. Half the time the answer to “is it sorted” hands you the pattern.
- Minutes 5 to 8, write test cases first. Jot two or three examples including an edge case (empty, single element, all duplicates). This shows you think before you type and gives you something to check against later.
- Minutes 8 to 12, name the approach out loud. Say “the brute force is O(n squared), but since it says longest substring, this is a sliding window, which gets us to O(n).” State the complexity before writing. This one sentence is what most interviewers are waiting to hear.
- Minutes 12 to 35, code while narrating. Talk through each line as you write it. If you get stuck, say what you are stuck on; a good interviewer will nudge you, but only if they know where you are.
- Minutes 35 to 45, test and state complexity. Walk your code through the examples you wrote at the start, fix what breaks, then finish with a clear “this is O(n) time and O(n) space.” End on that sentence and you close strong.
Say a candidate named Anvay and a candidate named Aviraj both reach the same correct sliding-window solution. Anvay typed in silence and only spoke at the end. Aviraj clarified the input, wrote two test cases, said “this is O(n)” before coding, and traced the examples at the finish. In practice Aviraj scores higher, because the interviewer saw the reasoning, not just the result. Think aloud is not a nice-to-have, it is the rubric.
Honest Scoping: What Finishes the Job
Let me be straight with you, because a lot of tutorials oversell this. Reading this post does not make you interview-ready. What it does is give you the map and the vocabulary so your practice is targeted instead of random. The pattern is the “aha,” but the speed and reflexes only come from reps. Realistically, budget six to eight weeks of steady practice, something like forty to sixty problems worked slowly and understood deeply, not two hundred rushed and forgotten.
Use free platforms for the reps. At the time of writing, LeetCode has the largest tagged problem set and lets you filter by pattern, NeetCode groups problems into exactly these pattern buckets with free explanations, HackerRank and Codewars are good for warmups, and Exercism gives you mentored feedback in Python at no cost. Any one of them works; the platform matters far less than the habit. Do a few problems a day, always name the pattern first, and always re-solve the ones you got wrong a week later. That spaced repetition is what turns “I have seen this” into “I can do this cold.”
Common Mistakes
❌ Mistake: Rebuilding the window sum from scratch every step
# Bad: sum() inside the loop makes an O(n) window an O(n*k) crawl
def max_window_slow(nums, k):
best = float("-inf")
for i in range(len(nums) - k + 1):
best = max(best, sum(nums[i:i+k])) # re-adds k items every step
return best
# Good: slide the window, add one and drop one, O(1) per step
def max_window_fast(nums, k):
window = sum(nums[:k])
best = window
for i in range(k, len(nums)):
window += nums[i] - nums[i - k]
best = max(best, window)
return best
Why: The slow version looks clean, but sum(nums[i:i+k]) re-adds all k items on every iteration, so a window of size k over n items does n times k work. The whole reason the sliding window exists is to avoid that. Add the entering element and subtract the leaving one, and each step is O(1). This is the single most common way people accidentally throw away the pattern’s benefit.
❌ Mistake: Writing DP recursion without a cache
# Bad: correct but exponential, recomputes the same subproblems forever
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
# Good: one decorator turns it into linear time
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
Why: A recursive solution with overlapping subproblems is exponential until you cache. The naive Fibonacci above takes hundreds of milliseconds by fib(32) and becomes unusable by fib(40). Adding @lru_cache is often the entire fix, turning O(2 to the n) into O(n) with one line. In an interview, write the recursion, then say “this recomputes subproblems, so I will memoize it,” and add the cache. That narration is worth real points.
Best Practices
- Classify before you code. Spend the first minute matching the problem to a pattern from the cheat sheet. The wrong pattern wastes twenty minutes; the right one makes the code fall out.
- State complexity out loud, before and after. Say the brute-force cost, say your target cost, then confirm it at the end. Interviewers gate on this even when your code is correct.
- Reach for a dict or set the moment you see repeated lookups. Most O(n squared) brute forces collapse to O(n) once a membership test becomes a hash lookup. This is the single most valuable habit in the whole toolkit.
- Write DP top-down first, then convert. Memoized recursion is easier to reason about; once it works, rewrite it as a bottom-up table if the interviewer wants O(1) space or you need to avoid recursion limits.
- Re-solve your mistakes on a delay. A problem you got wrong and re-solve a week later sticks far better than three new problems. Quality of reps beats quantity every time.
Wrapping Up
Coding interview patterns turn a scary, open-ended screen into a lookup problem: read the wording, name the shape, write the code you already know. You ran six of those shapes on Python 3.14.6 and watched them beat the brute force, most dramatically the memoized Fibonacci that went from 413 milliseconds to 60 microseconds by refusing to recompute what it already knew. The theme under all of them is the same: reuse work instead of repeating it, whether that is a window you slide, a prefix you precompute, a value you cache, or a table you fill once.
These coding interview patterns are evergreen, they predate every framework and will outlast them, and they work the same in Python 3.14.6 as they did decades ago. Learn the map, put in the reps, and narrate your thinking, and the coding round stops being a wall and starts being a checklist.
This post closes the Engineering Foundations run on algorithms and interview prep. If you want to jump to any other topic, browse the full Python + AI/ML tutorial series home.
Frequently Asked Questions
What are coding interview patterns?
Coding interview patterns are reusable problem-solving strategies, like two pointers, sliding window, fast and slow pointers, prefix sums, hashmap-first, and dynamic programming, that most LeetCode-style questions reduce to. Each pattern has a clue in the problem wording that tells you to use it, so recognizing the pattern lets you write the solution quickly and know its complexity in advance.
How many patterns do I actually need to know?
About eight to twelve core coding interview patterns cover the large majority of interview questions. The six in this post plus tree/graph traversal (BFS and DFS), heaps, and binary search handle most Easy and Medium problems you will see. Depth on a small set beats shallow exposure to many.
When do I use two pointers versus a hashmap for a pair-sum problem?
Use two pointers when the array is already sorted, because you get O(n) time and O(1) space by walking the ends inward. Use a hashmap when the array is unsorted and sorting it would be wasteful, because a single pass storing seen values also gets O(n) time, at the cost of O(n) extra space.
What is the difference between memoization and a DP table?
Both avoid recomputing subproblems. Memoization is top-down: you keep the recursion and cache each answer, often with functools.lru_cache. A table is bottom-up: you drop recursion and fill an array from the smallest case upward. Memoization is easier to write; a table avoids recursion limits and can be more space-efficient.
How long does it take to get interview-ready with these patterns?
Learning the patterns takes days, but building the reflexes takes six to eight weeks of steady practice, roughly forty to sixty problems worked slowly and understood deeply. Use free platforms, name the pattern before coding each problem, and re-solve the ones you miss a week later so they stick.
Interview Questions on Coding Patterns
Scenario questions, not trivia: this is the form this topic takes in a real interview.
Q: You are given an unsorted array and asked to find whether any two numbers sum to a target. Walk me through your approach and its complexity.
The brute force is a nested loop checking every pair, which is O(n squared). The better approach is hashmap-first: iterate once, and for each number compute the partner it needs (target minus the number), check whether that partner is already in a dictionary of seen values, and if so return the pair. Storing and looking up in a dict is O(1), so the whole thing is O(n) time and O(n) space. If the array were sorted instead, I would use two pointers for O(n) time and O(1) space.
Q: How do you find the longest substring without repeating characters, and why is it linear?
It is a variable-size sliding window. I move a right edge forward through the string and keep a dictionary of the last index each character appeared. When I hit a character already inside the current window, I jump the window’s start to just past its previous position. The best window length seen along the way is the answer. Each character is added and removed from the window at most once, so it is O(n) time, with O(k) space for the alphabet in play.
Q: Explain how the fast and slow pointer technique detects a cycle in a linked list.
You run two pointers from the head, one moving a single node per step and one moving two. If the list is straight, the fast pointer reaches the end and you conclude there is no cycle. If there is a loop, the fast pointer keeps circling and, because it gains one node on the slow pointer each step, it must eventually land on the same node, at which point they are equal and you report a cycle. It runs in O(n) time and, crucially, O(1) space, since you never store the visited nodes.
Q: A recursive solution is correct but times out. What is your first move?
I check whether the recursion has overlapping subproblems, the fingerprint of a dynamic programming problem. If the same arguments get recomputed, the fix is memoization: cache each result, in Python usually by adding functools.lru_cache to the function. That alone often turns exponential time into linear. If the interviewer wants to avoid recursion entirely or needs better space, I convert the memoized version into a bottom-up table that fills from the base cases upward.
Q: When would you deliberately not use a fancy pattern and just write the brute force?
When the input is guaranteed small and clarity matters more than scaling. Big O is about large n; for an array of ten items a clean nested loop can be both faster and more readable than a hash-based solution with more moving parts. I would still say out loud “the brute force is O(n squared), which is fine here because n is at most ten, but if the input could grow I would switch to a hashmap for O(n).” Showing you know the tradeoff is what earns the credit, not always reaching for the clever answer.
Q: How do prefix sums let you answer range-sum queries in constant time?
You precompute an array where each entry is the running total up to that index, which costs O(n) once. Then the sum of any range from index lo to hi is just the difference of two prefix entries, prefix at hi plus one minus prefix at lo, which is a single subtraction and therefore O(1) per query. It is the right pattern when you face many range-sum questions on the same unchanging array, since you pay the linear build once and every query afterward is free.
Reference: the complete, always-current details live in the official Python documentation.
Related Posts
Previous: Trees and Graphs in Python: BFS and DFS From Scratch
Next: Python Project: Build a Log Parser Command-Line Interface (CLI) (Regex + argparse)
Series Home: Python + AI/ML Tutorial Series

No comment