Filter concepts by levelShowing all levels.

Algorithms · How to Think About a Problem

A process, not a guess

What to do before, during, and after writing a solution

  • Restate firsta precise contract before any code
  • Size sets the barn tells you which complexity is allowed
  • Brute force firsta trusted reference before optimizing
  • Trace, don't assumewalk your own solution before trusting it

1.Restate the problem in your own words

Before writing any code, turn the prompt into a precise contract: exact inputs, exact output, and what happens on ties or missing matches. A vague restatement hides decisions you will otherwise make by accident while coding.

Key features

  • A precise restatement names the exact input types, the exact return type, and every edge case
  • Writing the restatement as a function signature and docstring forces you to answer questions prose lets you skip
  • If two people restate the same prompt differently, the prompt was ambiguous — resolve that before coding, not after

Command

python
def two_sum_indices(prices: list[int], target: int) -> tuple[int, int] | None:
    """Return the (i, j) indices, i < j, of the first pair of prices
    summing to target. Return None if no such pair exists."""

Write the restated problem as a function signature and docstring — if you cannot state the exact return type and edge-case behavior, you do not understand the problem yet.

2.Identify input size and constraints

A problem's stated limit on n (the input size) tells you which time complexities will actually finish in time. n up to 10^5 usually rules out an O(n^2) solution; n up to 20 usually means even O(2^n) is fine.

Key features

  • n ≤ ~20 hints at exponential (O(2^n)) solutions being intended, often backtracking
  • n ≤ ~1,000 usually allows O(n^2)
  • n ≤ ~10^5 or 10^6 usually requires O(n log n) or O(n)
  • A constraint on VALUES (not just count), like "1 ≤ nums[i] ≤ n", often hints at a specific trick — cyclic sort or counting sort instead of a general-purpose sort

Command

python
def choose_approach(n: int) -> str:
    if n <= 20:
        return "any correct approach is fine, even O(2^n)"
    if n <= 10_000:
        return "O(n^2) is safe"
    return "need O(n log n) or better"

Read n before you design an approach — it tells you which complexity classes are even allowed, before you write a line of code.

3.Work a small example by hand first

Before writing code, manually work through a small, concrete example — 3 or 4 elements, not 3 or 4 thousand. Doing this by hand exposes the actual steps your code will need before you have committed to any of them in syntax.

Key features

  • Pick an example small enough to trace by hand in under a minute — 3 to 5 elements is usually enough
  • Write down every intermediate value you check, not just the final answer
  • A small example is also your first test case, ready to paste into an assert once code exists

If you cannot trace your approach on a 4-element example by hand, you do not have an algorithm yet — you have a guess about syntax.

4.Identify the brute-force solution before optimizing

Write the slow, obvious solution first — usually checking every possibility — before trying to make it fast. It gives you a correct answer to compare against, even if it is too slow to ship.

Key features

  • The brute force is usually "try every possibility" — every pair, every subset, every starting point
  • It exists to be correct, not fast — its job is to be a trusted reference, not to ship
  • Once a faster approach exists, run both on the same inputs and compare results before trusting the faster one

Write the obviously-correct brute force first — it is the reference answer every faster rewrite has to match, not just outrun.

5.Name the pattern the problem resembles

Most problems are a known shape in disguise — a sorted-array pair search is two pointers, a "contiguous subarray satisfying X" is sliding window. Naming the pattern before coding tells you which technique to reach for.

Key features

  • A problem's phrasing is usually the clue: "sorted array, find a pair" signals two pointers; "top k" signals a heap
  • Naming the wrong pattern is still useful — it is quickly falsified by trying to force the problem into that shape
  • This is a lookup skill, built by exposure: the more named patterns you know, the faster you recognize one

State the pattern out loud before coding — "this is a sliding window problem" — even before you know the exact code, naming it narrows the search for how to solve it.

6.Consider edge cases

Decide what your solution does on an empty input, a single-element input, duplicate values, and negative numbers — before you finish writing it, not after a test fails.

Key features

  • Empty input: does the function return a sensible "no answer" value, or crash?
  • Single element: many two-element or pairwise algorithms need at least two items — check the boundary explicitly
  • Duplicates: does the algorithm assume every value is unique when the problem never promised that?
  • Negative numbers: an assumption like "sums only grow as you add more" breaks the moment negatives are allowed

Before calling a solution done, run it against empty input, a single element, duplicates, and negatives — the main path being correct says nothing about these.

7.Decide on a data structure before writing code

Choose the data structure a solution needs — list, set, dict, heap — as part of planning, not partway through typing the function. Switching midway is a sign the plan was never finished.

Command

python
prices = [7, 2, 9, 3, 1]
seen_prices = set(prices)      # decided up front: membership checks need O(1), a list gives O(n)
print(9 in seen_prices, 9 in prices)   # True True — same answer, different cost

If you catch yourself mid-function realizing "this needs to be a set, not a list" — stop and finish the plan before continuing to type.

8.Trace through your own solution before trusting it

After writing a solution, manually walk it through your own example, line by line, instead of just running it once and moving on. Tracing catches bugs that a single passing run can hide.

Key features

  • A trace checks EVERY line runs the way you expect, not just that a final answer looks right
  • Bugs that only show up on specific inputs (self-pairing, off-by-one bounds) often still "look right" on a quick glance
  • If tracing feels tedious, that is a signal the function is doing too much at once — smaller functions trace faster

A single run that returns something plausible is not proof of correctness — trace i, j and every intermediate value against your own example before you trust the result.

Remember

  • A precise restatement fixes exact input/output types and every edge case before any code exists.
  • n, the stated input size, tells you which complexity classes are even allowed.
  • A brute-force solution is a trusted reference answer, not a rough draft to be embarrassed by.
  • A single passing run is not proof of correctness — trace your own solution against your own example.

Pro tip

  • Pick a hand-trace example small enough to walk in under a minute — 3 to 5 elements.
  • Say the pattern out loud ("this is sliding window") even before you know the exact code.
  • The moment you think "this should really be a set" mid-function, stop and finish the plan first.

Next up

  • Big-O and complexity analysis
  • Arrays, strings and hashing

Sources

  • docs/content/sources/dsa-begginer-to-intermidiate.md · checked 2026-09-09

Spotted a mistake on this page? Tell us.