Skip to content
Algorithm Design

Algorithm Design

An algorithm is a finite, unambiguous sequence of steps that transforms defined inputs into desired outputs. Recipes, assembly instructions, and emergency checklists are algorithms for humans; code is algorithms for machines. Algorithm design is the pillar where the previous three pay off: decomposition gave you tractable pieces, pattern recognition matched them to known shapes, abstraction fixed the vocabulary — now you write the steps.

From steps to algorithms

A to-do list is not yet an algorithm. Four properties make the difference:

  1. Precision — every step is executable without judgment calls. “Season to taste” is fine for cooks, fatal for machines.
  2. Determinacy of flow — at every point it is clear what happens next, including for weird inputs. This is where if, loops, and error paths come from.
  3. Finiteness — it provably stops. Every loop needs a reason it terminates.
  4. Generality — it handles the class of inputs, not just Tuesday’s example.

The standard building blocks are famously few: sequence (do A then B), selection (if condition, do A, else B), iteration (repeat while condition), plus subroutines (name a chunk and reuse it — decomposition, again). Everything from spell-checkers to orbital mechanics compiles down to these.

Pseudocode: thinking without syntax

Pseudocode is deliberately informal notation for algorithms — precise enough to reason about, loose enough that you are not fighting a compiler. Here is a complete example solving a real problem: merge two sorted lists of appointments into one sorted list.

ALGORITHM merge_sorted(A, B)
  INPUT:  A, B - lists sorted by start time
  OUTPUT: M    - all items of A and B, sorted

  M <- empty list
  i <- 1                     # position in A
  j <- 1                     # position in B

  WHILE i <= length(A) AND j <= length(B):
      IF A[i].start <= B[j].start:
          append A[i] to M
          i <- i + 1
      ELSE:
          append B[j] to M
          j <- j + 1

  # one list is exhausted; copy the remainder of the other
  append A[i..end] to M
  append B[j..end] to M

  RETURN M

Check the four properties: every step is mechanical (precision); the IF covers both cases and the trailing appends cover exhaustion (determinacy); each loop pass advances i or j, so the loop must end (finiteness); nothing assumes list lengths or contents (generality). This ten-line shape — two pointers walking two sorted sequences — is a pattern worth naming and keeping: it reappears in merge sort, log-file merging, and calendar conflict detection.

Correctness: does it actually work?

An algorithm is correct when it produces the specified output for every valid input — not the inputs you happened to imagine. Three levels of assurance, in increasing rigor:

  • Testing — run it on chosen inputs, especially edge cases: empty lists, one element, all duplicates, already-merged input. Testing finds bugs but can never prove their absence.
  • Invariant reasoning — state something that is true on every loop iteration and check the steps preserve it. For merge_sorted: “M always contains, in sorted order, exactly the items consumed so far from A and B.” If the invariant holds each pass and at exit, correctness follows. This habit — even done informally in comments — catches whole classes of off-by-one and boundary bugs.
  • Specification first — you cannot be correct against a spec that does not exist. “Sorted by start time; ties broken by… ?” Deciding tie-breaking before coding is algorithm design; discovering it in production is incident response.

Complexity: how does it scale?

Correct is not enough; algorithms meet reality at scale. Big-O notation answers one question: when the input grows, how fast does the work grow? You need no math beyond noticing the shape:

GrowthNameFeel at n = 1,000,000
O(1)constantinstant, regardless of size
O(log n)logarithmic~20 steps — halving is magic
O(n)lineartouch everything once — fine
O(n log n)linearithmicgood sorting lives here
O(n²)quadratica trillion steps — coffee break or outage
O(2ⁿ)exponentialdo not wait

Conceptual rules of thumb: a loop over the input is O(n); a loop inside a loop over the same input is O(n²); repeatedly halving the search space is O(log n); “sort first, then scan” is O(n log n). Our merge_sorted touches each appointment exactly once — O(n) in the combined length — which is why calendars merge instantly while a naive “compare every pair of appointments” O(n²) approach would crawl.

Big-O intuition is a conversation skill as much as a coding skill. “That report joins every order against every customer in memory” is a sentence that lets a product manager understand why the feature dies at enterprise scale — before anyone builds it.

The design trade-off is real: the O(n²) version is often simpler to write and perfectly fine for n = 50. Choosing the simple-but-slower algorithm knowingly, with a note about the size at which it breaks, is good engineering. Choosing it unknowingly is a time bomb.

Designing, in practice

A compact workflow that scales from whiteboard to production:

  1. Specify inputs, outputs, and edge behavior in one paragraph.
  2. Match patterns — is this a search, a sort, a merge, a graph walk, a state machine? Reuse beats invention roughly always.
  3. Write pseudocode and argue its invariant in one sentence.
  4. Eyeball the complexity and compare against realistic input sizes.
  5. Trace it by hand on a tiny input and one nasty edge case.
  6. Only then, implement — or hand the pseudocode to an LLM, which will translate faithfully including your design mistakes. Generation is cheap now; steps 1–5 are the part that still requires you, as Enduring Skills argues in depth.

Verification of the result belongs to the next pillar: Evaluation & Debugging.

References