Code linearization

26 May, 2026 · 8 min read
Contents

You can find plenty of articles about design — where and how to use SQL, NoSQL, message queues, Redis, VMs, and so on. Almost nobody writes about tactics: the actual coding. It borders on style, but it isn’t just style. This is the first article in a series on tactics I use day to day. Highly opinionated — I don’t expect you to follow it. Look, chuckle, think about it, and use what you like.

Code linearization

The umbrella idea is linearization: keep control flow shallow and readable from top to bottom. Avoid deep nesting. Optimize the code manually by simplifying it — the compiler appreciates it, and so will you, next time you read it. Most of these techniques are language-agnostic. I’ll use JavaScript here because it’s the modern lingua franca.

The wider frame: dealing with complexity is a long-tail discipline, not a search for one-time wins. No single technique here saves you — the habit of applying all of them, across every line you write and read, is what keeps the mess from compounding.

None of this is new. Replace Nested Conditional with Guard Clauses is in Fowler’s refactoring catalog. What’s mine is the specific combination, the order in which I apply them, and the calls I make on edge cases.

And yet. Reviewing code is part of my daily work, so I see a lot of it. The missed opportunities — for simplification, for readability, for arguing correctness — are everywhere. Nesting ten levels deep is routine. The fix some teams reach for — extracting fragments of the pyramid into a dozen helper functions — doesn’t eliminate the depth, it exports it. The decision tree now spans files, and correctness analysis gets harder, not easier. If these tactics are obvious, why don’t more of us use them?

Booleans are booleans

Don’t do this:

const isReady = flag => {
  if (flag) {
    return true;
  } else {
    return false;
  }
};

Do this:

const isReady = flag => {
  return flag;
};

Better:

const isReady = flag => flag;

Best: don’t wrap it in a function at all. flag was already a boolean — the wrapper added zero information.

There’s a sharper reason than zero-information, too. Consider this:

const isPresent = hugeObject => { return hugeObject; };
const flag = isPresent(big);

isPresent doesn’t return a boolean — it returns its input. flag now holds big, and big can’t be garbage-collected for as long as flag is alive. A memory leak hiding behind a predicate name. If you genuinely need a predicate over a non-boolean input, coerce: const isPresent = obj => !!obj; (or Boolean(obj), or any comparison that returns a real boolean). More on this family of GC bugs in a future post.

Collapsing conditions

This technique removes unnecessary elses when the corresponding if body ends with a goto statement: return, break, or continue. It reverses if conditions when that simplifies the code, and collapses if/else to the ternary ?: when both branches assign to the same variable.

Example of if/else collapsing:

// original
if (flag) {
  // statements...
  return value;
} else {
  // other statements...
}

// transformed
if (flag) {
  // statements...
  return value;
}
// other statements...

Example of a tactical if reversal:

// original
if (flag) {
  // statements...
} else {
  return value;
}

// transformed
if (!flag) return value;
// statements...

Example of if/else to ternary:

// original
if (flag) {
  value = 1;
} else {
  value = 2;
}

// transformed
value = flag ? 1 : 2;

What does it buy us? Look at a realistic example from logistics:

const orderTransport = order => {
  if (order.distance > 1000) {
    if (order.weight > 100) {
      if (order.isUrgent) {
        return "air";
      } else {
        if (order.isOverseas) {
          return "sea";
        } else {
          return "train";
        }
      }
    } else {
      return "air";
    }
  } else {
    return "truck";
  }
};

Four levels of nesting. Let’s remove the unnecessary elses and reverse the outer two ifs:

const orderTransport = order => {
  if (order.distance <= 1000) return "truck";
  if (order.weight <= 100) return "air";
  if (order.isUrgent) return "air";
  if (order.isOverseas) return "sea";
  return "train";
};

Five lines, all at the same indentation level. Each line is a decision. Let’s combine a couple:

const orderTransport = order => {
  if (order.distance <= 1000) return "truck";
  if (order.weight <= 100 || order.isUrgent) return "air";
  return order.isOverseas ? "sea" : "train";
};

The behavior is identical. The intent is now obvious at a glance.

On if without braces

Some programmers insist if should always have braces — the worry is that if we add a statement later, we might misplace it relative to the unbraced body. Fair point in general. For the if/goto combination I make an exception: it makes no sense to add statements after return, break, or continue. If somebody does, they have bigger problems than missing braces.

So when an if body is a single goto statement, I drop the braces to reduce visual noise. Your linter may disagree; that’s a team call.

Named conditions

Precompute conditions into named locals and let the structure of the code reflect the structure of the decision.

We can rewrite our logistics function with this principle:

const orderTransport = order => {
  const isHeavy = order.weight > 100,
    isNear = order.distance <= 1000;
  if (isNear) return "truck";
  if (!isHeavy || order.isUrgent) return "air";
  return order.isOverseas ? "sea" : "train";
};

Not shorter, arguably more readable. The names label why, not just what.

The payoff grows when the same condition appears in multiple branches. Suppose marketing adds a bike option for light near-orders, and we want overseas heavy goods to ship by sea regardless of urgency:

const orderTransport = order => {
  const isHeavy = order.weight > 100,
    isNear = order.distance <= 1000;
  if (isNear) return isHeavy ? "truck" : "bike";
  if (isHeavy && order.isOverseas) return "sea";
  if (!isHeavy || order.isUrgent) return "air";
  return "train";
};

Now isHeavy appears in three decisions, isNear in one. The names earn their keep.

Pros: readability, intent is visible, the decision tree maps to lines you can read top to bottom.

Cons: locals are computed eagerly. If a condition is expensive (database call, sort, regex over a large string), you may pay for it on branches that don’t need it. Usually irrelevant; sometimes not. Measure if it matters.

A word on switch (true)

Occasionally you’ll meet code that uses switch as an if-cascade:

const orderTransport = order => {
  const isHeavy = order.weight > 100,
    isNear = order.distance <= 1000;
  switch (true) {
    case isNear:
      return isHeavy ? "truck" : "bike";
    case isHeavy && order.isOverseas:
      return "sea";
    case !isHeavy || order.isUrgent:
      return "air";
  }
  return "train";
};

What’s going on? Not every JS programmer knows that case accepts any expression — C and C++ allow only compile-time constants. JavaScript evaluates the switch expression once, then walks the case expressions top-down, comparing with === until one matches.

Notice the ===. case is not if. It does not tolerate truthy values; the expression has to evaluate to exactly the switch value — for switch (true) that means exactly true, not 1, not "yes", not a non-empty array, not an object. A common trap:

switch (true) {
  case order.errors.length:    // BUG: a number, never === true
    return "rejected";
  case order.warnings.length:  // same bug
    return "warned";
}

If you’re used to writing if (order.errors.length) and leaning on truthiness, switch (true) forces you to coerce explicitly — > 0, !!, Boolean(...), or any comparison that returns a real boolean.

When all case values are constants, an optimizer can pick a jump table or a binary search; with expressions it falls back to linear evaluation, equivalent to the if-cascade you’d have written anyway.

I rarely use this pattern. The reader’s eyes parse switch as “constant dispatch on a value” and have to re-parse on seeing switch (true). Many style guides and linters flag it. But it’s legitimate JS, fall-through still works if you need it, and some people genuinely find it more scannable than a stack of ifs. Know it exists, recognize it when you see it, and pick what your team will read fastest.

Summary

Linearization is a habit: keep the indentation shallow, let each line decide one thing, and let the structure of the code follow the structure of the decision. The transformations are small — flip an if, drop an else, name a condition — but small things compound. Skip them, and the mess grows.

We are conditioned to expect breakthroughs: a new framework, a new language, a switch from Docker Swarm to Kubernetes. Most of the time the truth isn’t in our tools — it’s in the code we write every day, and the tools won’t write it for us.

More on ifs and loops over the next few installments. Other faces of complexity-taming — dependency surfaces, API design, data shapes — come further out. Up next: break and continue are the new goto — Dijkstra revisited, and why structured jumps deserve more credit than the Considered Harmful tradition gives them.