Coding Interview Patterns in Python: Two Pointers, Sliding Window, DP

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.

‘longest / maxsubarray orsubstring’Sliding Windowslide a window,add new drop oldO(n)‘sorted array,find a pairor reverse’Two Pointersends move inwardon sorted dataO(n)‘linked listcycle ormiddle node’Fast / Slow1 step vs 2 stepsmeet inside a loopO(n), O(1) space‘many range-sumquestions onone array’Prefix Sumsprecompute runningtotals onceO(1) per query‘find a pair orcount in asingle pass’Hashmap Firststore seen values,look up the needO(n)‘count ways,fewest / most,overlapping subproblems’Dynamic Programmingmemo then table,reuse subanswersO(n) or O(n*m)Interview Pattern Map: What the Problem Says and Which Tool to Reach For

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.

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 tripleTwo pointersO(n)
Longest or shortest contiguous runSliding windowO(n)
Linked list cycle or midpointFast and slow pointersO(n), O(1) space
Many range-sum questions on one arrayPrefix sumsO(1) per query
Find, count, or dedupe in one passHashmap firstO(n)
Count ways, or fewest/most, overlapping subproblemsDynamic programmingO(n) to O(n*m)
Explore a grid, tree, or graph fullyBFS or DFSO(V + E)
Top k or streaming smallest/largestHeap (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.

#ProblemPatternApproach in one line
1Two SumHashmap firstStore each value, look up target minus value.
2Valid PalindromeTwo pointersCompare ends walking inward.
3Best Time to Buy and Sell StockSliding windowTrack lowest price so far, best profit after it.
4Longest Substring Without RepeatingSliding windowGrow the window, jump start past repeats.
5Linked List CycleFast and slowOne-step and two-step pointers, check if they meet.
6Subarray Sum Equals KPrefix sums plus hashmapCount prefix sums seen; look for current minus k.
7Group AnagramsHashmap firstKey each word by its sorted letters.
8Climbing StairsDynamic programmingWays(n) equals ways(n-1) plus ways(n-2).
9Coin ChangeDynamic programmingTable of fewest coins per amount, build upward.
10Number of IslandsDFS or BFS on a gridFlood-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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

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

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *