The shape is simple.
TigerBeetle's coding guide says to keep switch and if statements in a parent function when splitting code. Helpers should handle straight-line work. The guide names the rule "push ifs up and fors down."[1]
Matklad's original note gives both halves. A caller can reject an absent value before calling a function that accepts only a present value. A batch function can own the loop instead of making every caller repeat it. When one condition applies to a whole batch, placing the branch outside the loop avoids testing it for every item.[2]
Put the decision where it can be reviewed once. Put repeated work where it can be optimized as a batch.
The type can carry the check.
A function that accepts Option<Walrus> must decide what absence means. A function that accepts Walrus does not. Moving the absence check to the caller narrows the callee's valid input. The signature records that narrower contract.
This can make dead branches easier to see because the parent holds the control flow. It can also remove repeated checks. Neither result is automatic. If several callers need different absence policies, forcing one policy upward may make the parent harder to read.
Legality comes before speed.
Debasish Ghosh connects the idiom to query planning and filter-map laws. Database optimizers evaluate eligible selections near their inputs so later joins handle less data. The direction names differ because a query plan is drawn as a tree, but the practical move is the same: reduce the input before expensive combination.[3]
The article also states the limits. A condition can leave a loop only when it is invariant for that loop. A selection can cross a join only when it depends on columns available on the chosen side. Moving a filter ahead of a map preserves results only with the corresponding predicate transformation. It saves work only when that transformed predicate is cheaper than running the map.
Python's language reference makes clause order concrete. A comprehension nests its for and if clauses from left to right. Later clauses may depend on values produced by earlier ones. Reordering those clauses can change meaning, scope, or evaluation behavior.[4]
Review the effects too.
Equality on paper is not enough when a predicate, mapping function, iterator, or branch has side effects. Check exceptions, logging, mutation, ordering, and early exits. A rewrite that calls the same functions in a new order can produce a different program.
Keep one before-and-after fixture. Count calls to expensive work. Test empty input, one survivor, no survivors, and a boundary value. Benchmark only after the result and effects match.
Use this rewrite card.
BRANCH + BATCH REVIEW SCENARIO=[BRANCH IN LOOP / FILTER MAP / QUERY FILTER] INVARIANT_OR_DEPENDENCY=[EXACT CONDITION] ORIGINAL_ORDER=[CALL SEQUENCE] PROPOSED_ORDER=[CALL SEQUENCE] RESULT_FIXTURE=[INPUT + EXPECTED OUTPUT] EFFECT_FIXTURE=[CALLS + ERRORS + MUTATION] MEASUREMENT=[BASELINE + CANDIDATE] STATUS=[REWRITE CANDIDATE / NOT PROVEN]