Software Design Roadmap

Nine levels, each defined by what you can build and defend once you have it rather than by what you have read. The order matters: every level assumes the failure modes of the one before it, and the levels that look boring are the ones that decide whether the interesting ones work.

0 / 196 mastered0%
Level 1

Names, functions and the first two forces: cohesion and coupling

You can write a module a colleague can read once and predict correctly — names that come from the business rather than from the framework, functions whose signature already tells you what they can and cannot do, and units that keep related things together instead of scattering one rule across a folder. More importantly you can look at an existing file and say which of its parts belong together and which are only neighbours, which is the observation everything later in this domain is built on.

What Makes Software Hard to Change
The Cost of Change
Changeability Is the Goal
Local Reasoning
Naming
Naming and Domain Language
Units in Names and Types
Boolean Parameters
Function Design
Long Functions
Extract Function
Rename
Comments
Cohesion
Kinds of Coupling
Temporal Coupling
Shared-State Coupling
Duplicate Knowledge
DRY: Knowledge, Not Lines
KISS: Simplest for the Requirements You Have
What a Code Smell Is
Level 2

Responsibilities, module interfaces and state you can see

You can take a feature request, decide who owns each piece of it, and give that owner an interface narrow enough that callers cannot reach past it — and you can model the thing's lifecycle as named states with named transitions instead of five booleans that admit combinations nobody has thought about. The concrete deliverable at this level is a module whose invariants cannot be broken from outside it, which is the first design property a test can actually prove.

The Design Loop
Requirements Before Design
Constraints Are Part of the Design
Problem Decomposition
Designing by Responsibility
Single Responsibility, Carefully
Separation of Concerns
Encapsulation
Information Hiding
Designing a Module Interface
Exposing Too Much
Explicit State
State Machines
Invalid Transitions
Boolean Flag Explosion
State Ownership
Optional Values and Absence
Making Illegal States Unrepresentable
Invariants
Where Invariants Live
Enforcing Invariants
Error Modeling
Level 3

Abstraction, dependency direction, pattern vocabulary and the domain model

You can build a feature whose business rules do not import the database, the HTTP framework or the payment vendor — the dependency arrows point one way, on purpose, and you can say out loud why. You can also name what you built using the pattern vocabulary without pretending the name was the design, and you can tell a real abstraction (one variation you have actually seen twice) from a speculative one you added because it felt tidy.

What an Abstraction Actually Is
What an Abstraction Costs
The Rule of Three
Leaky Abstractions
Premature Abstraction
Choosing the Model
Dependency Direction
Dependency Inversion
Dependency Injection
Constructor Injection
Volatile Dependencies
Wiring and the Composition Root
Patterns as Vocabulary
Strategy
Factory
Adapter
Facade
Pattern Overuse
Domain Modeling
Ubiquitous Language
Entities
Value Objects
Level 4

Refactoring under tests, reading smells, and letting review and testing talk back

You can change the shape of working code without changing what it does — in small steps, each of them shippable, each of them green — and you can name the smell that told you to start. You can also read a hard-to-write test as a design report rather than a chore: when a unit needs six mocks to stand up, you know that is the design telling you where the hidden dependencies are, and you know which move fixes it.

What Refactoring Actually Is
The Refactoring Loop
Extract Module
Move Responsibility
Replace Conditional With Polymorphism
Introduce Parameter Object
God Object
Shotgun Surgery
Divergent Change
Feature Envy
Primitive Obsession
Long Parameter List
The Utility Dumping Ground
What Code Review Is For
A Review Checklist Worth Reading
Review Size
Tone, Disagreement and Receiving Review
Testing as Design Feedback
What a Unit Is
Mocking
Test Doubles, Precisely
Level 5

Package structure, architecture boundaries and the modular monolith

You can lay out a codebase so that a typical feature lands inside one directory instead of threading through four, and you can defend that layout against the change history rather than against a diagram. You can build a modular monolith whose modules have real contracts — enforced by the build, not by a convention nobody remembers — and you can tell an architecture boundary that earns its adapters from one that only adds a file to open.

Package Design
Package by Layer
Package by Feature
Vertical Slices
Module Granularity
Circular Dependencies
Breaking Cycles
Stable Dependencies
Dependency Cycles
Fan-in and Fan-out
Afferent and Efferent Coupling
Architecture Boundaries
Hexagonal Architecture (Ports and Adapters)
Clean Architecture, and Where It Is Overused
Onion Architecture
Boundary Adapters
Anti-Corruption Layer
Designing a Monolith
The Modular Monolith
Internal Module Contracts
Shared Libraries
The Common Module
Level 6

Legacy code, safe migration and debt you can actually account for

You can change a decade-old service that has no tests and no author left, safely: observe the behaviour, characterize it, find a seam, make the change behind it, and prove nothing else moved. You can run a migration in expand-and-contract steps where old and new coexist and every step is independently revertible, and you can keep a debt register that a product manager will fund because each entry names an interest payment rather than a preference.

What "Legacy" Actually Means
Characterization Tests
Seams
The Legacy Change Loop
Refactoring Without Tests
The Strangler Pattern
The Risk in a Rewrite
Incremental Migration
Designing the Migration
Backward Compatibility as a Constraint
Versioned Interfaces
Data Migration
Expand and Contract
Feature Flags and What They Cost
What Technical Debt Actually Is
Deliberate Debt
Accidental Debt
Interest: Why Debt Compounds
The Debt Register
Refactor or Rewrite
Deprecation
Level 7

Evolvability, the complexity budget and decisions you write down

You can predict, before you build, which future changes this design makes cheap and which it makes expensive — and say the second half out loud, because a design that is claimed to make everything cheap has not been understood. You can build an extension point that a second team can use without your help, spend a complexity budget deliberately instead of accidentally, and leave a decision record that tells the engineer in two years what would make this choice wrong.

Evolvability
Change Amplification
Encapsulation Radius
Extensibility
Plugin Architecture
Stability and Dependency Direction
Speculative Generality
Essential and Accidental Complexity
Simple Is Not Easy
The Complexity Budget
YAGNI, With Its Bill Attached
Over-Design and Under-Design
Over-Decomposition
The Trade-off Matrix
Decision Records
Revisit Triggers
Reversible and Irreversible Decisions
Build, Library, SaaS or Managed Service
Library or Framework
What a Framework Charges
Architecture Decision Records
Docs Close to Code
Level 8

Large refactors, aggregates, and the repository as a design artefact

You can plan and land a refactor too big for one pull request: sequence it into slices that each ship, keep the old and new shapes coexisting while it runs, and know at every point how to stop without leaving the codebase worse. You can also draw consistency boundaries around the data a rule protects, argue about SOLID in terms of the change it makes cheap rather than the acronym, and lay out a repository so ownership and review actually match the way the code changes.

Design, Architecture and System Design
Finding Seams
Stable Boundaries
Consistency Boundaries
Invariant Leaks
Aggregates
The Aggregate Root
Domain Services
The Anemic Domain Model
Transaction Script
When Domain-Driven Design Does Not Pay
SOLID, Read Honestly
Single Responsibility, Critically
Liskov Substitution, Critically
Interface Segregation, Critically
Dependency Inversion, Critically
How SOLID Gets Misused
Repository Structure
Monorepo vs Polyrepo
Code Ownership
Bus Factor
Design Review
Level 9

Designing a codebase that many teams change, and modernising one that already exists

You can design the parts of a codebase that outlive their authors: internal interfaces with a stated compatibility promise and a deprecation path, a feature-design template that forces states, failures and migration to be decided before code, and structures that keep one team's change from becoming another team's incident. You can also lead a modernisation — a strangler around a system nobody fully understands, delivered in increments that each pay for themselves — and know when the honest answer is that this system should be left alone.

When Design Does Not Pay
Requirements Are a Snapshot
The Requirements Nobody States
Design for the Known, Name What You Assumed
Dependency Management
Do We Need a Package for This?
API Stability
Semantic Versioning
RFCs
Knowledge Sharing
Designing a Feature Before Writing It
A Feature Design Template
Failure-Aware Feature Design
Slicing a Feature
Designing for Failure
Idempotency by Design
Partial Failure
What Changes at the Network Boundary
Debuggability by Design
Designing a System That Has a Model In It
The Model Is a Dependency
Where the Probabilistic System Ends