bigonotes
DSA: Python & HacksInterview Approach

How to Run a Coding Interview

A step-by-step flow for coding interviews, from clarifying the problem to testing, plus how to adapt it for codebase-style rounds.

6 min readLast updated Interview Approach

Interviewers grade how you think as much as whether your code passes. A candidate who clarifies, explains a brute force, improves it and tests it out loud usually beats one who silently writes a perfect answer. A fixed flow also keeps you calm: when you get stuck, you always know the next step.

The flow at a glance#

The whole flow fits in seven steps. Each one is covered in more detail below.

Step What you do What it sounds like
1. Clarify Restate the problem; pin down inputs, outputs, constraints "So I return the indices, not the values?"
2. Edge cases List the awkward inputs and agree how to handle them "What should happen on an empty array?"
3. Brute force Give the simplest correct idea and its complexity "Checking every pair is O(n2)O(n^2) time, O(1)O(1) space."
4. Optimise Find the bottleneck and the technique that removes it "The inner loop is a lookup, so a hash map makes it O(1)O(1)."
5. Commit Pick an approach, list data structures, outline steps, state complexity "Given nn up to 10510^5, I'll go with the O(n)O(n) version."
6. Code Write clean code while narrating "This map stores value to index."
7. Test Dry-run a normal case, then the edge cases "Walking through [2, 7, 11] with target 9..."

1. Clarify the problem#

Don't touch the keyboard yet. Restate the problem in your own words, then ask:

  • Inputs: types, sizes, can they be empty or None? Sorted? Unique values? Negative numbers?
  • Outputs: return a value, modify in place, or print? Any order required? What if there's no answer?
  • Constraints: how large is nn? The answer tells you the target complexity (see Which Technique When?).

2. List edge cases early#

Saying these out loud before coding shows you're thinking about correctness:

  • Empty input, or input of size 1
  • All elements the same, or all different
  • Duplicates
  • Negative numbers or zeros
  • Already in the final state (sorted, balanced, ...)
  • No valid answer: return -1, [], None, or raise?

Note them down, agree what each should return, and come back to them in step 7.

3. Start with brute force#

Explain the simplest correct solution and its time and space complexity, even if it's slow. It proves you understand the problem, gives you a fallback, and often points straight at the bottleneck. Walk through an example with it so you and the interviewer agree on the expected output.

"First, I can solve this by checking every pair, which is O(n2)O(n^2). Then I'll look at what's repeated."

4. Optimise the bottleneck#

Ask: which part of the brute force is doing repeated work?

  • A repeated search → hash map/set, sorting + binary search, or a heap.
  • Repeated range sums → prefix sums.
  • Recomputed subproblems → memoisation / DP.
  • A window that slides → two pointers or a sliding window.

If you're stuck:

  • Describe the solution in plain English as if doing it by hand, then translate.
  • Go through data structures one by one and rule them out: "Would a stack help? A heap?"
  • Try a smaller example, or a sorted version of the input.

Compare the options by time and space, then pick one:

"Considering nn can be 10510^5, I'll go with the hash map approach: O(n)O(n) time, O(n)O(n) space."

5. Commit before coding#

Before writing code, say briefly:

  1. Data structures you'll use and why (e.g. a frequency map, a min-heap).
  2. The algorithm in a few steps: initialise, loop, update, return.
  3. Time and space complexity, with the reason: "O(n)O(n) because each element enters and leaves the window once."
  4. "If this makes sense, I'll start coding." Waiting for a nod catches misunderstandings before they cost you 15 minutes.

6. Code it#

Now write the code you just outlined, keeping the interviewer with you as you go.

  • Keep narrating: what each variable means, why each condition is there.
  • Use clear names (left, right, seen, not i2, tmp).
  • Handle the agreed edge cases explicitly, often as early returns.
  • Re-read the code for syntax slips: missing colons, wrong indentation, off-by-one ranges.

7. Test it#

Don't wait for the interviewer to find bugs. Test your own code first:

  1. Dry-run the example from step 3, line by line, tracking variables.
  2. Run each edge case from step 2.
  3. Say: "That's complete. Let's run it against a test case." If you can execute code, use the template in Test Your Solution Live.

If the interviewer asks "can you make this faster?", look for repeated work, a lookup that could be a hash map or set, a search that could be binary, or branches you could prune.

Codebase-style rounds#

Some rounds give you an existing codebase or API instead of a blank editor ("implement get_execution_order using this service"). The same flow applies with two extra steps.

  1. Clarify requirements: input/output types, constraints, what counts as valid data, and how failures should be handled (retry? empty result? raise?).
  2. Read the code first: what each module is responsible for, what the API returns, and where your change belongs. Propose the location and confirm it before writing anything.
  3. Outline the core logic: state the algorithm, trade-offs and complexity; dry-run one happy example.
  4. Code in layers: fetch (with retries), sanitise, then run the core logic, returning early whenever there's nothing left to process.
  5. Dry-run the happy path, then the edge cases, and confirm the return format.

A template for the fetch → sanitise → core shape:

import time
from collections import deque
from typing import List


class PipelineScheduler:
    def __init__(self, api_service):
        self.api_service = api_service

    def get_execution_order(self, pipeline_id: str) -> List[str]:
        # 1. Fetch with retry and exponential backoff
        resp = None
        delay = 0.1
        for _ in range(3):
            resp = self.api_service.fetch_pipeline_tasks(pipeline_id)
            if resp and resp.status_code == 200:
                break
            time.sleep(delay)
            delay *= 3
        if not resp or resp.status_code != 200:
            return []  # fail gracefully after retries

        # 2. Extract and sanitise
        raw_tasks = resp.tasks or []
        valid_tasks = self._sanitize_tasks(raw_tasks)
        if not valid_tasks:
            return []

        # 3. Core logic: topological sort (Kahn's algorithm)
        indegree = {t["id"]: 0 for t in valid_tasks}
        children = {t["id"]: [] for t in valid_tasks}
        for task in valid_tasks:
            for dep in task["depends_on"]:
                if dep in indegree:
                    children[dep].append(task["id"])
                    indegree[task["id"]] += 1

        ready = deque(t for t, d in indegree.items() if d == 0)
        execution_order = []
        while ready:
            task_id = ready.popleft()
            execution_order.append(task_id)
            for child in children[task_id]:
                indegree[child] -= 1
                if indegree[child] == 0:
                    ready.append(child)

        has_cycle = len(execution_order) != len(valid_tasks)
        return [] if has_cycle else execution_order

    def _sanitize_tasks(self, raw_tasks):
        # Keep only well-formed tasks; drop anything without an id
        return [
            {"id": t["id"], "depends_on": t.get("depends_on") or []}
            for t in raw_tasks
            if isinstance(t, dict) and t.get("id")
        ]

Afterwards, talk about extensibility: what changes if requirements grow (new task fields, partial failures, very large pipelines)?

Naming that reads well#

Rule Weak Strong
Methods are verbs def data_sanitization(self, tasks) def _sanitize_tasks(self, tasks)
Collections are plural for item in task_list: for task in tasks:
Booleans ask a question if cycle_detected: if has_cycle:
Avoid generic names data = resp.data raw_tasks = resp.tasks
Name by state tasks = filter(tasks) valid_tasks = _sanitize_tasks(raw_tasks)

Key takeaways#

  • Clarify inputs, outputs and constraints before anything else; constraints hint at the target complexity.
  • Always state a brute force with its complexity, then optimise the bottleneck.
  • Commit out loud (data structures, steps, complexity) and get a nod before coding.
  • Narrate while coding, then dry-run a normal case and every edge case you listed.
  • In codebase rounds, read the existing code first and structure your change as fetch → sanitise → core logic with early returns.

Found a mistake? Tell me on LinkedIn ↗

Further reading#