Push Ifs Up and Fors Down

Ifs vs matches and exhaustiveness

  • Some argue the enum+match style is safer than if/else because the compiler enforces exhaustiveness; adding a new variant forces all matches to be updated.
  • Others counter that in simple cases this only adds boilerplate without extra safety, and compilers often generate identical machine code.
  • Debate centers on whether future changes justify the extra abstraction and “double-entry bookkeeping” of reifying conditions as enums.

Pushing ifs up: clarity, invariants, guard clauses

  • Supporters like centralizing branching in a higher-level function that “decides,” delegating straight‑line work to helpers.
  • Hoisting conditions out of loops can:
    • Make loop invariants explicit.
    • Simplify reasoning, debugging, and sometimes enable vectorization/parallelization.
  • Guard clauses and early returns are repeatedly praised as a way to avoid “arrow code” and deeply nested conditionals.

Arguments against universal “push ifs up”

  • Many see this as a context‑dependent heuristic, not a rule. Over-application can:
    • Violate DRY by forcing the same condition into many call sites.
    • Obscure local preconditions and make functions easier to misuse.
  • Some prefer validating close to where data is used, or keeping conditionals inside to guarantee idempotency or transaction boundaries.
  • Several note examples where domain invariants or framework behavior (e.g. routing, middleware, options parsing) naturally keep checks “down”.

Pushing fors down and batching

  • Strong agreement that APIs and functions should often operate on collections (“batch” style) rather than single items:
    • Enables single DB/HTTP calls instead of N+1 queries.
    • Better cache locality and easier loop optimization.
  • However, callers may need both per-item and batch semantics; sometimes the caller has better information about how to parallelize or group work.

Static analysis and cyclomatic complexity

  • Code-complexity tools often push the opposite direction (discouraging large, branchy “control centers”).
  • Many find cyclomatic complexity warnings noisy: they can fragment logic into many tiny “poltergeist” functions that are harder to follow.
  • Consensus: treat such tools as hints, not gospel; useful mainly to catch extreme cases (huge, deeply nested functions).

Types, input boundaries, and “parse don’t validate”

  • A recurring theme is moving checks toward input boundaries and encoding assumptions in types (e.g. Option<T> vs T, or separate “verified” vs “unchecked” types).
  • This reduces repeated conditionals in inner logic while preserving safety, especially in languages with rich type systems.
  • Some link this to the “parse, don’t validate” idea: normalize data once at the edges, then operate on stronger types internally.

Performance vs readability and context

  • There’s disagreement on how performance‑driven the advice is:
    • Some read it as primarily about clarity and expressing intent; performance is a side effect.
    • Others emphasize that in many domains (hot loops, data pipelines, SwiftUI rendering, SIMD) hoisting branches and batching are crucial to throughput.
  • Several commenters insist that in typical application/server code, readability and maintainability dominate small performance gains.

General sentiment

  • Many like the heuristic (“push ifs up, fors down”) as a mental nudge to reconsider structure, not as doctrine.
  • Others see it as oversimplified, similar to other programming “fads”: useful in certain performance‑sensitive or data‑processing contexts, but dangerous if applied blindly.