PushBackLog

Keep It Simple, Stupid (KISS)

Advisory enforcement Complete by PushBackLog team
Topic: quality Topic: supportability Methodology: KISS Skillset: any Technology: generic Stage: execution Stage: review

Keep It Simple, Stupid (KISS)

Status: Complete
Category: Clean Code
Default enforcement: Advisory
Author: PushBackLog team


Tags

  • Topic: quality, supportability
  • Methodology: KISS
  • Skillset: any
  • Technology: generic
  • Stage: execution, review

Summary

Systems work best when they are kept simple. Complexity should not be introduced until it is necessary to solve an actual problem. The simplest solution that works is almost always preferable to a clever one.


Rationale

Complexity is the root cause

In software engineering, most problems that are expensive to solve — bugs, slow delivery, costly onboarding, risky refactors — have complexity as their root cause. KISS is not an argument for lazy or crude solutions. It’s an argument that the irreducible complexity of the problem domain is hard enough, and that every layer of accidental complexity added on top compounds the cost of every future change.

Fred Brooks called this “accidental complexity” (complexity we create for ourselves) versus “essential complexity” (intrinsic to the problem). KISS is a discipline for holding accidental complexity to zero.

Clever code is not a compliment

In most engineering disciplines, a design praised as “clever” is a good thing. In software, “clever” code is often code whose workings are non-obvious, whose assumptions are implicit, and that cannot be maintained by someone who didn’t write it. Clever code is a short-term display of individual intelligence at the long-term expense of the team. The goal is code that a junior engineer can understand and extend without risk.

Complexity compounds in AI-assisted codebases

With AI pair programming, complex code gets replicated and extended faster than ever. If the underlying design is unnecessarily complex, AI tools will faithfully reproduce that complexity in every generated variation. Simple, clear code acts as a clean template; complex code acts as a virus that propagates through the codebase at machine speed.


Guidance

Simplicity tests

Before committing a solution, apply these tests:

QuestionIf “no”, simplify
Can I explain this in one sentence?
Would a new team member understand this in under 5 minutes?
Is every variable, function, and class doing one clear thing?
Is there a simpler data structure that would work?
Have I solved the problem I actually have, not the one I anticipate?

Practical approaches

  • Favour flat over nested: deeply nested conditions and loops dramatically increase cognitive load. Each nesting level doubles the mental context required.
  • Solve today’s problem: resist building configuration hooks, plugin systems, and extension points for requirements you don’t have yet (see YAGNI).
  • Name clearly: a well-named variable or function reduces the need for comments and makes the intent obvious. Clarity is a form of simplicity.
  • Prefer standard libraries: the simplest implementation of many algorithms is the one that uses the standard library correctly, rather than a custom implementation.
  • Delete unused code: deleted code is the simplest code. Code that isn’t there can’t have bugs.

Examples

Over-engineered vs simple

// Over-engineered: configurable, extensible, future-proof
class StatusTransitionEngine {
  private transitions: Map<string, TransitionConfig>;
  register(from: string, to: string, config: TransitionConfig) { ... }
  execute(from: string, to: string, context: TransitionContext): TransitionResult { ... }
}

// Simple: solves the actual problem
type OrderStatus = 'pending' | 'confirmed' | 'shipped' | 'delivered';

function canTransition(from: OrderStatus, to: OrderStatus): boolean {
  const allowed: Record<OrderStatus, OrderStatus[]> = {
    pending: ['confirmed'],
    confirmed: ['shipped'],
    shipped: ['delivered'],
    delivered: [],
  };
  return allowed[from].includes(to);
}

The second version is testable in 4 lines, readable in 10 seconds, and is the actual interface you need today.

Nested conditionals vs early return

// Complex nesting
function process(order: Order) {
  if (order) {
    if (order.status === 'confirmed') {
      if (order.items.length > 0) {
        // actual logic buried 3 levels deep
      }
    }
  }
}

// Simple: guard clauses
function process(order: Order) {
  if (!order) return;
  if (order.status !== 'confirmed') return;
  if (order.items.length === 0) return;
  // actual logic at top level
}

Anti-patterns

1. Premature abstraction

Building frameworks, plugin systems, and configurable engines before the requirements are understood. If you’ve never run this code in production, you don’t know what needs to be flexible.

2. “Clever” one-liners

Code compressed into the minimum possible lines using nested ternaries, chained optional operators, and bitwise tricks. Saves keystrokes once; costs comprehension every subsequent reading.

3. Abstraction for its own sake

Wrapping a simple 3-line operation in a class, a factory, and an interface because patterns. Every abstraction has a carrying cost — it must be justified by a genuine benefit.

4. Configuration-driven complexity

Replacing clear branching logic with a configuration file that requires understanding both the file format and the engine that reads it. Often introduced under the name “flexibility”.

5. Dead code left “just in case”

Commented-out code, unused feature flags, and unreachable paths. None of them are simple. All of them raise questions and add cognitive load.



Part of the PushBackLog Best Practices Library. Suggest improvements →