Algorithms · Lesson 17 of 17
How to Solve Coding Interview Problems
A step-by-step method for coding interview problems: clarify, find patterns, pick data structures, analyse Big O, code cleanly and test out loud, in Python.
- Intermediate
- 18 min read
- 4 objectives
Before this lessonLesson 16: String Algorithms: KMP and Rabin-Karp
What you will learn
- Follow a repeatable six-step method in a live interview
- Map problem clues to the algorithm patterns in this track
- Use constraints to predict the required complexity
- Move from brute force to an optimised solution out loud
Your Progress
0 of 17 lessons 0%
- Lessons0 / 17
- Completed0
- Est. time left~ 5 hours
Create a free account to keep your progress on every device.
Tip: pressing Next marks this lesson complete automatically.
Knowing algorithms is only half of passing a coding interview. The other half is showing how you think under time pressure: asking the right questions, starting simple, improving step by step and catching your own bugs. Interviewers at most companies in 2026 score communication, problem solving, code quality and testing separately, and a candidate who talks through a correct O(n log n) solution often beats one who silently writes the optimal code with a bug.
This lesson gives you a repeatable method and ties together everything in this track. We will walk one problem through every step.
The six-step method
- 1. Clarify. Restate the problem. Ask about input size, empty input, duplicates, negatives, sorted or not, and what to return if there is no answer.
- 2. Examples. Work one normal example and one edge case by hand. This often reveals the pattern.
- 3. Brute force. Say the obvious solution and its complexity, even if it is slow. It proves you understand the problem and gives you a fallback.
- 4. Optimise. Find the bottleneck and match it to a pattern. Agree on the approach with the interviewer before coding.
- 5. Code. Write clean code with good names and small helper functions. Narrate as you go.
- 6. Test. Trace your examples through the code line by line, then edge cases. State final time and space complexity.
Let the constraints tell you the complexity
Input size is a huge hint. A typical judge or interviewer expects roughly 108 simple operations to be fine. Work backwards:
import math
limits = [(10, "O(n!) or O(2^n): backtracking, bitmask"),
(20, "O(2^n): subsets, bitmask DP"),
(500, "O(n^3): interval DP, Floyd-Warshall"),
(5_000, "O(n^2): nested loops, 2D DP"),
(1_000_000, "O(n log n) or O(n): sorting, heaps, two pointers"),
(10**18, "O(log n): binary search, fast power, math")]
for n, advice in limits:
print(f"n <= {n:<20,} ~{advice}")
print(f"{1_000_000 * math.log2(1_000_000):,.0f} steps for n log n at n = 1e6")n <= 10 ~O(n!) or O(2^n): backtracking, bitmask n <= 20 ~O(2^n): subsets, bitmask DP n <= 500 ~O(n^3): interval DP, Floyd-Warshall n <= 5,000 ~O(n^2): nested loops, 2D DP n <= 1,000,000 ~O(n log n) or O(n): sorting, heaps, two pointers n <= 1,000,000,000,000,000,000 ~O(log n): binary search, fast power, math 19,931,569 steps for n log n at n = 1e6
Map clues to patterns
After a few dozen problems you will notice that the same phrases keep pointing to the same tools. This table links each clue to the lesson in this track that covers it.
- Sorted array, pair or triplet sums, in-place changes: two pointers.
- Contiguous sub-array or substring with a condition: sliding window.
- Sorted data, "minimum value that works", monotonic yes/no: binary search.
- "Have we seen this before?", counts, fast lookup: a hash map or set.
- Top k, k-th largest, merge k lists, running median: a heap.
- Fewest steps, levels, grids, connectivity: BFS/DFS. Weighted edges: Dijkstra.
- Dependencies, ordering, prerequisites: topological sort.
- "All combinations/permutations": backtracking.
- "Number of ways", "min/max cost" with overlapping choices: dynamic programming.
- Matching brackets, "next greater element", undo: a stack.
Worked example: steps 1 to 3
Problem: given a list of daily revenue changes (can be negative) and a number k, count how many contiguous stretches of days sum to exactly k.
Clarify: Can values be negative? (Yes.) Can the list be empty? (Yes, return 0.) Up to how many days? (105.) Negatives rule out a sliding window, and 105 means O(n2) = 1010 is too slow, so we are aiming for O(n) or O(n log n).
Brute force: try every start and end, keeping a running sum. Say it out loud: "This is O(n2) time and O(1) space. It works but is too slow for 105; let me write it anyway so I can check the faster version against it."
def count_k_brute(nums, k):
count = 0
for start in range(len(nums)):
total = 0
for end in range(start, len(nums)):
total += nums[end]
if total == k:
count += 1
return count
print(count_k_brute([3, 4, -7, 3, 1, 3, 1, -4, -2, -2], 7))3
Worked example: steps 4 to 6
Optimise: the bottleneck is re-summing ranges. With prefix sums, the sum from i+1 to j is prefix[j] - prefix[i]. We want that to equal k, so for each j we need to know how many earlier prefixes equal prefix[j] - k. That is a "have we seen this before?" question, so use a hash map of counts.
from collections import defaultdict
def count_k(nums, k):
seen = defaultdict(int)
seen[0] = 1 # empty prefix, so ranges can start at 0
prefix = count = 0
for x in nums:
prefix += x
count += seen[prefix - k] # earlier prefixes that complete a range
seen[prefix] += 1
return count
data = [3, 4, -7, 3, 1, 3, 1, -4, -2, -2]
print(count_k(data, 7))
print(count_k([], 3), count_k([1, 1, 1], 2), count_k([0, 0], 0))3 0 2 3
Test: trace [1, 1, 1], k = 2 by hand: prefixes are 1, 2, 3; at prefix 2 we find one earlier 0, at prefix 3 we find one earlier 1, total 2. Check edge cases: empty list, all zeros with k = 0 (three ranges in [0, 0]), negatives. Then state it: O(n) time, O(n) space.
Test against the brute force
A habit that separates strong candidates (and strong engineers): when you have a slow but obviously correct solution, use it to check the fast one on random inputs. In an interview you describe this; in practice you run it.
# from earlier in this lesson
def count_k_brute(nums, k):
count = 0
for start in range(len(nums)):
total = 0
for end in range(start, len(nums)):
total += nums[end]
if total == k:
count += 1
return count
from collections import defaultdict
def count_k(nums, k):
seen = defaultdict(int)
seen[0] = 1 # empty prefix, so ranges can start at 0
prefix = count = 0
for x in nums:
prefix += x
count += seen[prefix - k] # earlier prefixes that complete a range
seen[prefix] += 1
return count
# new code
import random
random.seed(7)
for trial in range(500):
nums = [random.randint(-5, 5) for _ in range(random.randint(0, 12))]
k = random.randint(-6, 6)
assert count_k(nums, k) == count_k_brute(nums, k), (nums, k)
print("500 random tests passed")500 random tests passed
When you are stuck
- Solve a tiny example by hand and watch what you do; that is often the algorithm.
- Ask "what if the input were sorted?" or "what if I had a hash map of everything so far?"
- Walk the pattern list above and ask whether each one fits.
- Simplify: solve it for k = 1, or for one dimension, then generalise.
- Ask for a hint. Interviewers expect it, and using a hint well is a positive signal.
Recap
- Follow clarify, examples, brute force, optimise, code, test, every time.
- Input size tells you the target complexity before you pick an algorithm.
- Recurring phrases map to recurring patterns: windows, binary search, BFS, DP, heaps, hash maps.
- Always state a brute force and its Big O first; then improve the bottleneck.
- Trace your code on examples and edge cases, and finish by stating time and space.
# Write your solution here
Finished reading? Mark this lesson complete to track your progress.
