Same Input, Same Output
Why a tiny, checkable surface area matters more than where a value lives, and the exact definition a pure function call has to satisfy every single time.
Last time, trusting a constant meant checking the whole program for any line that might reassign it. That's fine for a short file. It falls apart fast in a real one.
Here's a version that fixes that problem entirely.
function makeDiscounter(rate) {
return function discount(price) {
return price - price * rate;
};
}
const tenPercentOff = makeDiscounter(0.1);
tenPercentOff(100); // 90discount reads rate from outside itself. rate is a parameter, not a constant. By the raw rule, that looks like a side cause.
A Smaller Place to Check Beats a Bigger One
Ask the real question instead: where in this program could rate possibly change?
Only one place. The single line where makeDiscounter(rate) receives it. Nothing inside discount reassigns it. Nothing outside can reach in and touch it either.
Compare that to a top-level constant sitting in a hundred-line file. Checking rate here takes one glance at one function.
That "how many places could this change" question has a name: surface area. It just means how many spots in your code could possibly touch a value. Shrinking the surface area where reassignment could happen is what actually earns your trust, constant keyword or not.
The inner function still remembers rate after makeDiscounter has already finished running. That trick has a name, closure, and it gets a full post of its own soon. For now, the part worth keeping is simpler: handing in one value early and getting back a function that remembers it is also called partial application, and this series comes back to it properly later.
A Function Reading a Property Isn't Automatically Safe
Now a different shape of function:
function getPrice(item) {
return item.price;
}Call getPrice(item) with the same item object, over and over. Same output every time?
It depends on what you can't see from these two lines alone.
If item never gets mutated anywhere in the program, yes. If .price is a plain property, yes.
But say .price is actually a getter, and that getter calculates a live, fluctuating price based on current demand:
const item = {
get price() {
return basePrice + Math.random() * surgeMultiplier;
},
};Same call. Different number every time. Your confidence in getPrice didn't change, but the moment you saw what .price really was, it collapsed to zero.
The Actual Definition
This is the piece the last four posts were building toward.
A pure function call: same input, same output, every single time, no exceptions.
Not "usually." Not "unless something weird happens elsewhere." Every time, guaranteed, or it doesn't count.
discount(100) earns that guarantee because there's nowhere for rate to hide a change. getPrice(item) only earns it once you've confirmed item is exactly as boring as it looks.
Why the Surface Area Is the Whole Game
Neither of these examples was provable by staring at four lines in isolation. Both needed you to check what's reachable from outside.
The entire practical skill of writing pure functions is shrinking that reachable area until checking it is trivial. A parameter closed over by one small function beats a constant sitting in a big shared file. A plain property beats a getter with hidden logic behind it.
Once the surface area is small enough, a reader, or your future self in six months, can glance at a function and just know. That's the trust this whole series has been chasing since the first post.
Trusting a function this way still has limits. Some calls will look pure but leave you only partly convinced, and knowing exactly how confident you should be is the next thing worth pinning down.
Keep reading