The 'Push Ifs Up, Fors Down' Idiom Has an Algebra—and Limits

Push Ifs Up and Fors Down: The Idiom, Its Algebra, and Its Limits

Debasish Ghosh explores the programming heuristic 'push ifs up and fors down' from TigerBeetle's Tiger Style and matklad's blog. He traces the same pattern in database query optimization (push selections/projections down, defer joins, vectorized execution) and in functional programming: pushing an if up is restricting a function to a subobject, while filter/map rewriting follows from the naturality of catMaybes. The algebra reveals when rewrites are legal and when they actually save work.

So, it's the algebra that tells you which rewrites are legal. In the filter/map case above, it's the naturality of catMaybes that drives the legality and helps you reason about the overall structure of the code.
  1. socializer

    I am continually impressed by the ability of LLMs to take trivial ideas and turn them into lengthy and obtuse blog posts with unnecessary analogies.

  2. hatthew

    Are we talking about this from the perspective of CS (algorithm optimization) or SE (code design)?

    From an SE perspective, make a flatmap function that explicitly handles Collection<Optional<Walrus>>. The implementation doesn't matter. If your language/framework already has a compatible flatmap function, make a single frobnicate(Optional<Walrus>) function that returns whatever value is necessary for flatmap(frobnicate) to discard them.

    From a CS perspective, doing a filter from Collection<Optional<Walrus>> to Collection<Walrus> is probably a bad idea. If your collection is small, nothing matters. If your collection is large, you probably don't want to spend time making a new copy of it. If your filter just returns a view rather than a hard copy, then there is no optimization benefit and you should just do whatever makes the most sense from an SE perspective. If frobnicate is cheap then you're paying the branch prediction failure tax anyway regardless of when you frobnicate, and if frobnicate is more expensive then your should probably parallelize and have each thread handle unpacking the Optional. Either way, you probably don't want to spend time making a copy.

    These are all generalizations based on hypotheticals and there are certainly a lot of exceptions, but broadly speaking I don't see a strong argument here. If optimization matters then optimize based on your own profiling of your situation, and if optimization doesn't matter then design your functions based on what feat […]

  3. rtpg

    I've always believed the opposite: get conditionals deep in your code so that the higher level control flow is regular.

    But I suppose my greater philosophy for making code that avoids bugs is that you have a couple things that are done when dealing with data:

    - distribution

    - deciding

    And you want to avoid distribution and deciding being mixed together in the same spot.

    "Distribution" can be for loops but also breaking up some data based on some key into N bistinct buckets

    "Deciding" is where you're looking at the data more closely to make some decision (like "is this a big customer or a small customer")

    Distribution often involves decision making, but if you mix them all in one spot you can obfuscate your decision points. Splitting it up just makes things "obviously" right or "obviously" wrong. Perf stuff is another discussion of course, but in practice most things are not at a scale where it matters.

    by_category = defaultdict(list)

    for d in data:

    by_category[category(d)].append(d)

    for category, per_category_data in by_category.items():

    do_thing(category, per_category_data)

    I really value code patterns that make mistakes obvious, or at least makes it harder to stuff a mistake in somewhere. Some patterns are harder to describe in this model though.

    (I do like the advice of having a consistent vocabulary for working on collections as a principle though, I just find that top-level conditional use tends to quickly get you into "... why is this method n […]

  4. ivanjermakov

    Save some time and read the original post instead: https://matklad.github.io/2023/11/15/push-ifs-up-and-fors-do...

  5. gorgoiler

    Erm, no? You write f(w: Walrus) -> Walrus and then let the caller handle Walrus|None and Iterable[Walrus] however they wish!

    And if someone decides the codebase needs an abstraction over (and therefore specific functions to handle) Iterable[Walrus|None] then you check the weather and suggest they take a break and go for a stroll. (You check the weather to see if you should lend them your brolly.)

    What am I missing?

More from this day

2026-10-07