All levels

Data Structures and Algorithms Interview Prep

Twenty-one chapters from how to run a coding interview to every core pattern: arrays, windows, stacks, trees, graphs, backtracking and dynamic programming, with tested Python and diagrams.

Chapter 1 of 21Foundations · How to Approach a Coding Interview

How to Approach a Coding Interview

A coding interview is a short, structured conversation about solving a problem with code. Interviewers are not only checking whether you can reach the answer. They watch how you think, how you communicate, whether you can turn an idea into correct code, and how you react when you are stuck. Candidates with strong algorithms knowledge still fail by starting to code too early, going silent, or never testing their solution.

This chapter gives you a loop to run on every problem, a way to read constraints, and a communication style that makes your reasoning visible. The rest of the track teaches the patterns that fill in step four of the loop.

All code in this track is Python, written to be short and readable. The ideas carry directly to Java and C++, and where a language detail changes the answer, the text says so.

1. What is being assessed

Most interviewers score some version of four things:

  • Problem solving. Can you break an unfamiliar problem down, find a workable approach and improve it?
  • Coding. Can you write correct, readable code without heavy help, in a reasonable time?
  • Verification. Do you test your code on examples, find your own bugs, and reason about edge cases?
  • Communication. Can the interviewer follow your thinking and work with you?

Notice what is missing: memorising the exact solution. Interviewers prefer a candidate who reasons their way to a good answer over one who recites an answer they have seen. If you do recognise a problem, say so honestly and explain the idea. Pretending not to have seen it is not required, and getting caught pretending is far worse.

2. The loop

<!--fig:steps-->
1 Understand restate, inputs, edge cases 2 Examples small cases by hand 3 Brute force say it, state its cost 4 Optimise find the bottleneck 5 Implement clean, named, incremental 6 Verify trace an example, complexity The loop to run on every problem, out loud Talk through each step. A correct brute force said early beats a clever idea you cannot finish. Figure 1. A repeatable approach for any coding problem.

Step 1: understand the problem

Restate the problem in your own words and ask questions before touching code. Good questions are specific:

  • What are the input types and sizes? Can the array be empty? Can values be negative or repeated?
  • What should be returned if there is no answer: None, -1, an empty list?
  • Is the input sorted? Can I modify it, or must I preserve it?
  • Are there memory limits? Is the data in memory?
  • If several answers exist, should I return any or a particular one?

The interviewer's answers often rule out half of the edge cases. They also show that you care about the real problem and not only the example.

Step 2: work through examples

Take the example given, and solve it by hand, noticing what you do. Then make a second example that is smaller, and a third that is an edge case: empty, one element, all equal, already sorted, maximum size. Hand-solving examples often reveals the algorithm, because the steps you take naturally are the steps your code needs.

Step 3: say the brute force

State the simplest correct approach, even if it is slow, and give its time and space complexity. This does three things: it proves you understand the problem, it gives you a fallback if you cannot improve it, and it sets up the next step, because the brute force's bottleneck points to the optimisation.

Step 4: optimise

Ask: where is the wasted work? Typical answers:

  • The same value is recomputed many times. Use a hash map, memoisation or a precomputed table.
  • A search repeats over a structure that could be ordered. Sort, or use binary search, a heap or a tree.
  • A nested loop compares every pair where a single pass with a data structure would do.
  • The answer can be built incrementally (greedy or dynamic programming) rather than by trying everything.

The patterns in the next chapters are the common ways to remove that waste.

Step 5: implement

Write the code in small, named pieces. Use clear variable names, since the interviewer reads your code as you write it. If a step is complicated, write a helper function with a descriptive name, and fill it in afterwards. Narrate briefly as you go: "I keep a dictionary from value to index, and for each number I check whether its complement is already there."

Avoid clever one-liners. Correct and clear beats short and obscure.

Step 6: verify

Trace your code on the example, line by line, tracking variable values. Then run an edge case. Interviewers look for this. Finding your own bug during a trace is a strong signal. Having the interviewer find it is a weak one. Finish by stating the time and space complexity and why.

3. A worked example, start to finish

Problem. Given a list of integers and a target, return the indexes of two different elements that add up to the target. Assume exactly one solution exists.

Understand. Return indexes, not values. Two different positions, so the same element cannot be used twice. Exactly one solution, so I do not need to handle none or many.

Examples. [2, 7, 11, 15] with target 9: indexes 0 and 1. A tricky one: [3, 3] with target 6 must return 0 and 1, so duplicate values are possible.

Brute force. Check every pair.

def two_sum_brute(nums, target):
    n = len(nums)
    for i in range(n):
        for j in range(i + 1, n):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []

assert two_sum_brute([2, 7, 11, 15], 9) == [0, 1]
assert two_sum_brute([3, 3], 6) == [0, 1]

This is time and space. For 100,000 elements that is five billion pair checks, too slow.

Optimise. The wasted work is the inner search: for each number, I scan the rest for target - x. If I remembered what I have already seen, I could look up the complement in constant time. A hash map from value to index does that.

def two_sum(nums, target):
    seen = {}                      # value -> index of an earlier element
    for i, x in enumerate(nums):
        need = target - x
        if need in seen:
            return [seen[need], i]
        seen[x] = i
    return []

assert two_sum([2, 7, 11, 15], 9) == [0, 1]
assert two_sum([3, 3], 6) == [0, 1]
assert two_sum([3, 2, 4], 6) == [1, 2]

Verify. Trace [3, 3] with target 6. At i = 0, x = 3, need = 3, seen is empty, so store seen[3] = 0. At i = 1, x = 3, need = 3, which is in seen at index 0, so return [0, 1]. The duplicate case works because I check for the complement before storing the current element, so an element is never paired with itself.

Complexity. One pass, so time. The dictionary holds up to entries, so space. The trade is memory for time, which is the most common trade in this track.

Notice the shape of the conversation: brute force first, the bottleneck named, one idea to remove it, a trace, complexity. That is the loop.

4. Reading the constraints

Problem statements usually state input sizes, and those numbers tell you the intended complexity. A rough rule is that a judge allows on the order of simple operations per second in a compiled language, and fewer in Python, so lean toward the lower rows.

<!--fig:constraints-->
Read the constraint, guess the intended complexity (rough rule for about 1 second) Input sizeTargetTypical techniques n up to 10O(n!)all permutations n up to 20O(2^n)subsets, bitmask search n up to 500O(n^3)triple loops, Floyd-Warshall n up to 5,000O(n^2)double loops, simple DP n up to 10^6O(n log n)sorting, heaps, binary search n up to 10^7 or moreO(n)one pass, hashing, two pointers n astronomically largeO(log n) or O(1)binary search, maths Figure 2. Constraints hint at the intended solution. Python is slower than compiled languages, so lean to the lower rows.

Use it in both directions. If is and you have an idea, it will be too slow, so look for an approach. If is 20, an exponential search is intended, and an elaborate polynomial algorithm is probably over-engineering.

5. Communication that works

Think aloud, in short sentences. Silence for two minutes is unsettling. Saying "I am considering sorting first, which costs , but let me check whether a hash map avoids it" lets the interviewer follow and steer.

State assumptions. "I will assume the input fits in memory." If you are wrong, you find out early.

Invite collaboration. "Does it matter to you whether I optimise for time or space here?" Interviewers often have a direction in mind, and asking is allowed.

Handle hints gracefully. A hint is not a failure. It means the interviewer wants you to succeed. Take it, say what you understood, and apply it.

When you are stuck:

  1. Say so, and say what you have tried.
  2. Go back to a small example and solve it by hand.
  3. Simplify: solve a special case (sorted input, no duplicates), then generalise.
  4. Ask which pattern the problem resembles: two pointers, hashing, recursion, a graph.
  5. Offer the brute force and improve from there.

6. Writing code under observation

  • Start with the function signature and the return type, and confirm it with the interviewer.
  • Handle the base case and empty input first. It makes the rest simpler and shows care.
  • Name things for meaning. left, right, window_sum, seen beat a, b, c.
  • Keep one idea per loop. If a loop does three things, split it.
  • Use the language well. In Python, know enumerate, zip, collections.Counter, defaultdict, deque, heapq, slicing and tuple unpacking, and know their costs: slicing copies, list.pop(0) is , and x in some_list is while x in some_set is on average.
  • Do not leave dead code or print statements. Clean up as you finish.

7. Testing without a compiler

You often write code on a shared editor without running it, or you may be able to run it. Either way, test deliberately.

  1. Trace the given example by hand.
  2. Try the empty and single-element input.
  3. Try a case with duplicates, negatives or the largest value.
  4. Try a case that hits each branch. Every if should execute at least once in your tests.
  5. Check indexes at the boundaries. Off-by-one errors are the most common bug: is it < or <=, range(n) or range(n + 1)?

When you find a bug, fix it calmly and say what it was. Everyone writes bugs. The signal is how you find and fix them.

8. Analysing complexity out loud

State time and space at the end, and justify them with the structure of the code, not a guess:

  • A single loop over items with constant work inside: .
  • A loop inside a loop over the same data: .
  • A loop that halves the problem each time: .
  • A sort: .
  • Recursion: the number of calls times the work per call, and remember the call stack space.

Mention both average and worst case when they differ, as with a hash map (constant on average, linear in the worst case) or quicksort.

9. Mistakes that cost offers

  • Coding before understanding. The fix is the first two steps of the loop.
  • Not stating the brute force. You skip a free demonstration of understanding.
  • Going silent. The interviewer cannot give credit for thoughts they cannot hear.
  • Ignoring edge cases and discovering them only when the interviewer proposes one.
  • Never testing. An untested solution often has an off-by-one.
  • Arguing with a hint. Take it and use it.
  • Over-optimising too early. Get a correct solution on the board, then improve it.
  • Memorised solutions you cannot explain. The first follow-up question exposes them.

10. How to practise

Practice is deliberate, not volume for its own sake.

  1. Learn the patterns in the following chapters, in order. Each is a family of problems.
  2. For each problem, set a timer of 25 to 35 minutes, and follow the loop out loud, even alone. Speaking while solving is a skill that needs practice.
  3. If you are stuck for 20 minutes, read the hint, not the solution. Then try again. Reading a full solution too soon removes the learning.
  4. After solving, review. What pattern was it? What was the key insight? Could you have got there faster? Write one line in a notebook.
  5. Re-solve problems a week later without looking. If you cannot, the pattern has not stuck.
  6. Do mock interviews with a friend or a tutor, because performing under observation is different from solving alone.
  7. Practise in the language you will use, until its common operations and their costs are automatic.

11. What is expected at each level

Entry level. You solve standard problems, such as arrays, strings, hash maps and simple trees, with a correct approach and clean code, with some prompting for optimisation and edge cases.

Mid-level. You solve medium problems independently, discuss trade-offs, optimise from brute force without help, test your code and analyse complexity correctly.

Senior. You solve hard problems or unfamiliar variants, recognise the underlying pattern quickly, discuss alternatives and their costs, write production-quality code, and anticipate follow-ups such as scaling or streaming input.

12. Quick answers to common questions

Should I code in the language I know best? Yes. Pick the one in which you can write correct code quickly, and know its standard library.

Should I memorise solutions? No. Learn patterns and their reasoning. A memorised solution fails on the first twist.

What if I cannot find the optimal solution? Present the best you have, explain the limit and what you would look into next. A correct brute force with a clear analysis is better than a broken clever solution.

What if I make a mistake? Say so, fix it, move on. Calm correction is a positive signal.

How do I handle a problem I have seen before? Say you have seen something similar, explain the idea and implement it carefully. Honesty is respected, and you are still evaluated on the implementation and the follow-ups.

Header Logo