Lesson 1 of 7
How to Think About Algorithms
"An algorithm must be seen to be believed." — Donald Knuth
You already write code that works. You have shipped features, fixed bugs, and reasoned about types across three volumes. So why open a whole new volume with a chapter about thinking? Because the gap between a working programmer and a strong engineer is not knowing more syntax — it is having a reliable way to attack a problem you have never seen before. That is what an algorithm is, and that is what an interviewer is really probing when they slide a problem across the table. The rest of this volume teaches you specific structures and techniques. This chapter teaches you the mindset that makes all of them click — and the habit that will carry you when the pattern isn't obvious.
Faced with a problem you have never seen, what do you actually do — how do you get from a blank editor to a correct, efficient solution, and how do you know it's good?
Core Concepts
What an algorithm actually is
Strip away the mystique and an algorithm is a precise, finite sequence of steps that transforms an input into an output. Every word in that sentence earns its place. Precise: each step is unambiguous — no "and then figure it out." Finite: it must end; a recipe that never finishes is not a recipe. Input → output: it takes something in and produces something definite in return. A recipe for jollof rice is an algorithm. Long division is an algorithm. So is the code that turns a search query into a ranked list of results.
That definition is liberating, because it means solving a problem is not a flash of genius — it is specifying the steps clearly enough that a machine could follow them. Interviewers know this. When they ask you to find duplicates or reverse a list, they are not testing whether you have memorised the trick. They are watching you turn a vague request into a precise procedure.
What makes an algorithm good
Working is the floor, not the ceiling. Three qualities separate a good algorithm from one that merely runs, and they come in strict priority order:
- Correctness — first, always. An algorithm that produces the wrong answer quickly is worthless. Before you optimise anything, it must be right for every valid input, including the awkward ones.
- Efficiency — time and space. Once it's correct, how does the work grow as the input grows? And how much extra memory does it consume? These are the two costs you trade against each other for the rest of your career.
- Clarity — can a human read it? Code is read far more often than it is written. Of two solutions with the same correctness and complexity, the clearer one is the better algorithm — it is the one your team can maintain.
Beginners chase efficiency first and get burned. Strong engineers lock down correctness, then reach for speed, then clean it up. Hold that order; the whole volume rests on it.
Think of getting across Lagos. Your first job is to actually arrive at the right address — a route that lands you somewhere else is useless no matter how fast it was (correctness). Then you care whether it took forty minutes or three hours, and how much fuel you burned (time and space). Finally, you want directions you can explain to a friend without a whiteboard (clarity). Nobody brags about a shortcut that dropped them in the wrong city.
The central habit: there is always more than one way
Here is the single most important shift in this volume. A problem does not have an answer — it has a space of answers, and your job is to compare them. The moment you catch yourself thinking "the solution is…", correct it to "one solution is…, and I wonder if there's a better one." That reflex — comparing approaches out loud — is exactly what distinguishes candidates in an interview and engineers in a design review.
This gives us the universal method, the one you will lean on when nothing else comes:
Brute force first, then optimize. Get a correct solution — however clumsy — then improve it.
The brute-force solution is the obvious one: try everything, check every pair, simulate every step. It is usually slow and that is fine. Its job is to (a) prove you understand the problem, (b) give you a correct baseline to test against, and (c) reveal the wasteful part you can then attack. Candidates freeze because they hunt for the clever answer immediately. Strong candidates say "let me start with the naive version and improve it" — and are never stuck, because there is always a brute force.
Seeing it: two approaches to one problem
The problem: does this array contain any duplicate value? Watch the method in action — a correct brute force, then a better idea it points us toward:
Same inputs, same answers — but different shapes. The brute force does, in the worst case, about n²/2 comparisons; the fast version does one pass of n. On five items you cannot feel it. On a million, one is a blink and the other is a coffee break. Notice what the second approach spent to get its speed: memory, in the form of the Set. That is the trade — space for time — and recognising when it's worth making is a huge part of what's coming in the chapters ahead. We are not cataloguing that pattern here; we are noticing that comparing two correct approaches is how you find it at all.
The loop a strong engineer runs on every new problem:
• Restate it in your own words. Confirm the input, the output, and the awkward cases before touching the keyboard. Half of "hard" problems are just under-read.
• Write the brute force, out loud. Get correct first. Say "this is O(n^2), and I think we can do better" — you've now shown correctness and awareness.
• Find the waste, then attack it. Where is the repeated work? That's almost always where a data structure or a smarter pass buys you a lower complexity.
• Test on tiny inputs by hand. Empty, one element, all-the-same. If it survives those, it usually survives the rest.
Why we measure with Big-O, not a stopwatch
You will notice we described those two functions with O(n²) and O(n), not "1.3 milliseconds." That is deliberate, and it is the bridge into the next chapter. A stopwatch measures your laptop, on this input, today. Change the machine, the language, or the mood of the operating system and the number changes — it tells you nothing durable. Big-O measures how the work grows as the input grows, which is hardware-independent and honest about scale. It answers the only question that survives contact with production: what happens when the data gets big?
Keep it intuitive for now. When you double the input, does the work roughly stay flat (O(1)), double (O(n)), or quadruple (O(n²))? That single instinct — how does the cost react to growth — is enough to reason well until the next chapter makes it rigorous. Both time and space get measured this way; the Set above cost us O(n) extra space to save O(n²) time, and Big-O is the language that lets us weigh one against the other without ever running the code.
The word "algorithm" comes from a person. In 9th-century Baghdad, the mathematician Muhammad ibn Musa al-Khwarizmi wrote a book of step-by-step procedures for solving equations; Latin translators rendered his name as Algoritmi, and it stuck to the idea of a mechanical procedure itself. His other book gave us "algebra." A thousand years before the first computer, he established the thing this whole volume is about: that a problem can be solved by a fixed, repeatable sequence of steps anyone — or anything — can follow. The machine came later; the idea came first.
Correctness tools you can trust
If correctness comes first, you need ways to establish it that don't rely on hope. Three tools recur through this volume:
- Edge cases. The input that breaks naive code is rarely the typical one. It's the empty array, the single element, all-duplicates, negatives, the already-sorted list. Enumerate these before you code — they are also the follow-ups an interviewer has ready.
- Invariants. An invariant is a condition that stays true on every pass of a loop. "After processing index
i,seencontains exactly the values fromnums[0..i]" is the invariant that makeshasDuplicateFastobviously correct. Find the invariant and the proof writes itself. - Testing on small inputs. Trace the algorithm by hand on three or four elements. If it's right on
[],[1], and[1, 1], you have covered most of the ways it could be wrong.
When a student finishes a lesson, the Academy (a Next.js + Drizzle/MySQL app) has to decide whether they've now completed the whole course. The brute-force instinct is to re-scan every lesson and every one of that student's completion records and cross-check them each time a page loads — correct, but O(lessons × records) work on every request. The better shape stores a running count and compares it against the course total: one cheap check. Same correct answer, a fraction of the work — and the deciding move was simply asking "is there a cheaper way to get the same result?" instead of shipping the first thing that worked.
Common Mistakes
- Optimising before it's correct. Reaching for the clever
O(n)trick before the brute force even runs. You end up with fast, confidently wrong code. Correct first; the naive version is your test oracle. - Assuming one right answer. Locking onto the first idea and defending it. There is always more than one approach — not comparing them is the mistake, in interviews and in code review alike.
- Skipping the edge cases. Code that's flawless on
[3, 7, 1]and throws on[]or[1]. Empty and single-element inputs are where naive solutions go to die. - Confusing "runs fast on my input" with "is efficient." Your test array has ten items; production has ten million. A stopwatch on small data hides an
O(n²)cliff. Reason about growth, not this one run. - Going silent while you think. In an interview, unspoken reasoning is invisible reasoning. The mistake isn't a wrong first guess — it's not narrating the path that would let you correct it.
- Ignoring space. A slick
O(n)-time fix that quietly allocatesO(n)memory can still fall over at scale. Both costs count.
You're asked to find the two numbers in a list that add up to a target. The brute force is obvious: check every pair — O(n^2). Now, before reading any further in this volume, ask the one question that unlocks nearly every optimisation: "what am I re-computing that I could instead remember?" For each number x, you're searching the whole list for target - x. What if you had already stored every number you'd seen, so that search was instant? You don't need the machinery yet — just feel how "remember instead of re-search" collapses an O(n^2) problem toward O(n). That instinct, not the specific trick, is the thing this volume is really teaching.
Interview Corner
O(n^2) time, O(1) space. The waste is that I re-scan values I've already seen. So I'll trade space for time: keep a Set of seen values and check membership in one pass — O(n) time, O(n) space. I'd confirm edge cases: empty array and single element both return false." n is tiny — then the brute force's simplicity and O(1) space can win. It depends on the constraints."- In the editor above, add a third input — an empty array
[]and a single-element array[4]— and run both functions on them. Confirm both returnfalse, and say out loud why an empty array can never contain a duplicate. - Take a problem you can already solve — say, finding the largest number in a list. Write the brute-force solution, then state its time and space complexity out loud as if in a micro1 screen. Ask yourself the notebook question: is there any repeated work to remove?
- Write one sentence describing the invariant of the loop in
hasDuplicateFast— the condition that stays true after each pass. If you can state it cleanly, you have just proved the function correct.
Chapter Summary
- An algorithm is a precise, finite sequence of steps transforming input into output — solving problems is specifying steps clearly, not waiting for genius.
- A good algorithm is, in strict order: correct first, then efficient (time and space), then clear. Never optimise something that isn't yet right.
- There is always more than one approach; the core habit is to compare them out loud rather than defend the first idea.
- The universal method is brute force first, then optimize — a correct naive solution proves understanding, gives you a test oracle, and reveals the waste to attack.
- We measure with Big-O, not a stopwatch, because growth is hardware-independent and honest about scale — the instinct is "how does the cost react when the input doubles?"
- Establish correctness with edge cases, invariants (a condition that holds every loop pass), and testing on small inputs.
- In an interview, the deliverable is how you reason, not just the final answer — make your method visible.
We now have the mindset: correct first, compare approaches, brute force then optimize, and reason about growth rather than a stopwatch. But "reason about growth" was left deliberately intuitive here — and intuition breaks down on the hard cases. To compare algorithms with confidence, you need the precise language for it: not just an upper bound, but tight and lower bounds too. That's exactly where we go next, in Big-O, Big-Θ & Big-Ω — the formal grammar of how algorithms scale.
Editorial reflection — the temptation, opening a data-structures volume, is to rush past "thinking" to get to the "real" content — the trees and the graphs and the clever tricks. Resist it. Every technique in the chapters ahead is an answer to a question this chapter taught you to ask: is there a cheaper way to get the same correct result? Engineers who memorise the tricks solve the problems they've seen. Engineers who internalise the method solve the ones they haven't — and that, far more than any single algorithm, is what a career and an interview are actually testing.
Q&A · 0
Enrol to ask questions and join the discussion.
No questions yet — be the first to ask.