Clean Code

Avoiding Negative Conditions: Best Practices with TypeScript

I once spent 20 minutes debugging a conditional that read if (!isNotDisabled). Three negations deep. My brain short-circuited.

19 Apr 2024

Avoiding Negative Conditions: Best Practices with TypeScript

I once spent 20 minutes debugging a conditional that read if (!isNotDisabled). Three negations deep. My brain short-circuited.

Negative conditions force readers to mentally invert logic. That's cognitive overhead you don't need. Your code should read like a statement, not a riddle.

What negative conditions look like

A negative condition checks for the absence of something. It uses !, not, or !== to negate a boolean expression.

Typescript
// Negative — requires mental inversion
function isNotAdmin(user: User): boolean {
  return user.role !== 'admin';
}

if (!isNotAdmin(user)) {
  // Wait... so they ARE an admin?
}

Compare that to the positive version:

Typescript
// Positive — reads like plain English
function isAdmin(user: User): boolean {
  return user.role === 'admin';
}

if (isAdmin(user)) {
  // Clear. No mental gymnastics.
}

Why this matters

Positive conditions read top-down. You see isAdmin and you know what's being checked. You see !isNotAdmin and you have to pause, invert, then proceed.

That pause multiplies across a codebase. Across a team. Across years of maintenance.

Practical guidelines

Flip the name. If you find yourself writing isNotX, ask whether isX makes more sense. It almost always does.

Use early returns for the negative path. Instead of wrapping your main logic in if (!error), return early on the error and keep the happy path unindented.

Typescript
// Instead of this
function processOrder(order: Order) {
  if (!order.isCancelled) {
    // ... 40 lines of logic
  }
}

// Do this
function processOrder(order: Order) {
  if (order.isCancelled) return;

  // ... 40 lines of logic, no nesting
}

Watch for double negatives. !isInvalid, !hasNoPermission, !isNotReady — these are all signs you should rename the boolean.

The trade-off

Positive conditions are easier to read. But sometimes a negative check is the natural way to express a guard clause: if (!user) return. That's fine. The goal isn't to eliminate every ! — it's to make sure your reader never has to decode your intent.

Write conditions that a teammate can understand at a glance. If they have to pause and invert, rename it.