programming
software-development
code-quality
complexity
best-practices

How complex should code be?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Code should be as simple as possible while still expressing real business rules and operational constraints. The goal is not minimum line count, but minimum accidental complexity. Good teams accept essential complexity where needed and aggressively remove complexity caused by unclear structure, duplication, and weak boundaries.

Essential vs Accidental Complexity

A useful decision framework is to separate two categories:

  • Essential complexity: required by domain rules, safety, or performance constraints.
  • Accidental complexity: introduced by design choices, poor naming, or unnecessary abstractions.

Example of essential complexity:

  • payment workflows with retries, idempotency, fraud checks, and audit trails

Example of accidental complexity:

  • deeply nested conditionals duplicating the same checks in many files

The main engineering job is not eliminating all complexity. It is concentrating complexity where it belongs.

Start with Readability as a Constraint

Readable code lowers defect rate and onboarding cost.

python
# Less clear style
if u and u.active and (u.role == "admin" or (u.role == "ops" and u.oncall)):
    allow = True
python
1# Clearer style
2
3def can_access_admin_panel(user) -> bool:
4    if not user or not user.active:
5        return False
6
7    is_admin = user.role == "admin"
8    is_oncall_ops = user.role == "ops" and user.oncall
9    return is_admin or is_oncall_ops

The second version is slightly longer but easier to reason about and test.

Hide Necessary Complexity Behind Stable Interfaces

Complex internals are acceptable when public interface remains clear.

java
public interface PaymentGateway {
    PaymentResult charge(ChargeRequest request);
}

Implementation can include retries, timeout policies, and fallback providers. Callers still use a small stable API surface.

This boundary-based approach keeps complexity local and prevents it from spreading across the codebase.

Use Metrics as Signals, Not Laws

Complexity metrics help identify risk areas, but they do not replace engineering judgment.

Useful signals:

  • cyclomatic complexity per function
  • nesting depth
  • churn on same files
  • bug density by module

Example Python command:

bash
pip install radon
radon cc -s -n B src/

A high score is a prompt for review, not automatic rewrite.

Refactor Incrementally

Overly complex code should be improved in small safe steps.

Practical sequence:

  1. add tests around existing behavior
  2. extract named helper functions
  3. remove duplication
  4. isolate side effects from pure decision logic
  5. simplify data flow and dependencies

Small refactors reduce risk and are easier to review than large rewrites.

Performance Tradeoffs

Sometimes a simpler algorithm is slower, and performance goals matter. Introduce extra complexity only when profiling proves need.

Guidelines:

  • profile realistic workloads first
  • document why optimization is necessary
  • keep benchmark evidence near the optimization
  • preserve readability with comments and helper names

Without evidence, complexity added for theoretical speed often becomes long-term maintenance debt.

Team Practices That Control Complexity

Complexity is influenced by team habits as much as code style.

Effective practices:

  • code review checklists for readability and branching clarity
  • architecture notes for major abstractions
  • periodic cleanup of high-churn modules
  • explicit conventions for naming and error handling

These habits create predictable code structure and lower cognitive load.

When High Complexity Is Acceptable

Some modules will stay complex due to domain requirements. That is acceptable when:

  • responsibilities are clear
  • tests are strong
  • failure modes are documented
  • change impact is understood

Complex code with clear boundaries and excellent tests is often healthier than oversimplified code that hides critical rules.

Common Pitfalls

  • Equating fewer lines with better code quality.
  • Adding abstractions too early before real duplication appears.
  • Ignoring domain complexity and forcing unrealistic simplification.
  • Optimizing without profiling evidence.
  • Leaving complex modules under-tested.

Summary

  • Aim for minimal accidental complexity, not minimal line count.
  • Keep essential complexity localized behind stable interfaces.
  • Prioritize readability and explicit intent.
  • Use metrics and profiling to guide refactoring decisions.
  • Improve complex code incrementally with tests and review discipline.

Course illustration
Course illustration

All Rights Reserved.