A good cook doesn't start frying and then wonder whether they've chopped the onions. They read the whole recipe first: they line up the ingredients, get the oven hot, and set aside the sauce they made yesterday so they don't have to make it again. Long before any real work happens, they've already thought about what each step needs and what they can prepare or reuse.
This is a genuine computational-thinking skill, and it is exactly how experienced programmers work. Before writing a line of code, they ask two forward-looking questions:
Neither question is about the step in front of you. Both are about planning ahead — and getting them right is often the difference between a solution that is correct and fast and one that is buggy and slow. This page is about learning to think that way on purpose.
A precondition is a condition that must hold before a step is allowed to run, or the step will fail or produce a wrong answer. Almost every operation you can think of quietly assumes one:
Binary search is the classic exam example. It halves the search space each step by comparing with the middle element and deciding which half the target must be in. That reasoning is only valid if the list is sorted; on an unsorted list it will confidently throw away the half that actually contains the answer and report "not found". The sortedness isn't a nice-to-have — it is the precondition that makes the whole method correct.
Thinking ahead means spotting these before you run the step, and arranging for them to be true: sort the list first, check the file exists, guard against a zero divisor. A step whose preconditions you've secured is a step you can trust.
A precondition is a statement that must be true immediately before an operation runs for that operation to behave correctly.
Let's watch binary search on a sorted list, then on the same numbers left unsorted. The code is identical; only the precondition changes. Press Run.
The second search fails silently. It doesn't crash; it just lies — it says 34 isn't there when it plainly is. That's the danger of an unmet precondition: the code runs, so nothing looks wrong, but the answer is garbage. Thinking ahead, we'd sort the list before searching it, restoring the precondition and making the result trustworthy again.
The second half of thinking ahead is refusing to repeat expensive work. If a result is costly to compute and you'll need it again, compute it once, store it, and look it up next time. That stored copy is a cache, and the trade is always the same: you spend a little memory to save a lot of time.
You already rely on caches all day long:
average() once, then call it everywhere rather than re-deriving the loop each time.Here is a precomputed lookup table beating recomputation. Imagine we repeatedly need factorials. Working one out means a loop of multiplications; if we know in advance we'll only ever ask about the numbers 0–12, we can precompute all of them once into a table, then answer every future request with an instant array lookup. A counter proves how much work each approach does.
The table pays a small fixed cost up front and then answers every request for free. The more times the answers are reused, the more the precomputation pays off — which is the whole point of thinking ahead: the up-front effort is an investment against work you can see coming.
A game might rotate hundreds of objects sixty times a second. Working out Math.sin()
from scratch that often adds up. So many games build a lookup table once when they
start — the sine of every whole-degree angle, say — and thereafter just read the answer straight out
of the array. It's the same bargain as the factorial table: a one-off precomputation buys instant
answers forever after. Old hardware relied on this trick so heavily that lookup tables were
practically a programming style.
Reuse isn't only about caching values — it's also about reusing code. When you notice two steps need the same calculation, thinking ahead means writing it once as a subroutine and calling it from both places, rather than copying the logic. You get the work, and the testing of that work, for free the second time.
Notice the last line reuses average() to average two averages. Because the subroutine
already exists and is trusted, building bigger things out of it costs almost nothing. That's reuse as
a design habit: solve a piece well once, then lean on it.
Thinking ahead has two failure modes, and both are favourites in exams.
The common thread: both are the price of planning ahead. A cache and a precondition are each a promise about the world — "this stored answer is still current", "this list really is sorted" — and a promise you don't keep is worse than one you never made.