Six months is enough time to forget not just how a piece of code works, but why it exists at all.
And why a code exists is usually a factor of context.
This article is about the kind of documentation that endures time, refactoring, and even AI assistance because it preserves what code alone cannot.
Why Most Documentations Fail
Most discussions around documentation start with the wrong premise: That documentation exists to explain how code works.
In reality, engineers can read and understand the code.
What they can't recover is:
What constraints shaped the solution
What alternatives were considered
What trade-offs were accepted
What breaks if something changes
While those are the details that matter most when maintaining a system, they are also the details that disappear first.
Documentation that fails usually shares a few common patterns:
1. It repeats what the code already says
If your documentation can be regenerated from the code, it will eventually drift because it explains how, not why.
2. It describes ideal behaviour, not real behaviour
Docs often describe how the system should behave, not how it actually behaves under edge cases, load, or legacy constraints.
3. It lives too far from the code
If documentation requires switching tools, tabs, or mental context, it won't stay updated.
Documentation Hierarchy
Documentation exists in layers and with different lifespans. Understanding which layer you're working in determines how you write it.
Layer 1: Code-Level Context (Lifespan: Until next refactor)
Lives as inline comments next to the code it speaks to. No context switching required. These should be brief and survive refactors by being anchored to why, not how.
// This exists to prevent duplicate API calls during route transitions.
// Removing this reintroduces a race condition seen in production.
Layer 2: Feature-Level Boundaries (Lifespan: Across multiple refactors)
Lives in README files at module/feature boundaries. These explain the problem being solved, not the implementation.
Example structure:
## What This Solves
The payment reconciliation module exists because third-party payment
gateways don't guarantee webhook delivery order. We needed a way to
handle out-of-sequence events without data corruption.
## What It Doesn't Do
We explicitly don't handle refunds here because refund logic lives
in the accounting service to maintain compliance audit trails.
## Key Assumptions
- All transactions are in UTC
- Exchange rates are immutable once set
- Webhook retries follow exponential backoff (handled by gateway)
Layer 3: System-Level Invariants (Lifespan: Years)
These are the truths that span multiple components. If these change, the entire system needs rethinking. ADRs work well for Layer 3 documentation
// SYSTEM INVARIANT:
// User sessions must ALWAYS be validated before accessing financial data.
// This is enforced at the middleware level AND in each financial controller.
// Redundancy is intentional - removing either layer violates PCI compliance
Effective documentation requires using the right layer for the right purpose: inline comments for immediate decisions, READMEs for feature context, and system invariants for architectural truths. Layer 3 is particularly valuable because it captures what won't change even if you rewrite the entire application in a different stack.
Evaluating Your Documentation: Key Indicators
Onboarding Friction
Documentation serves as asynchronous knowledge transfer. If your team needs to message someone to understand code, then your documentation needs a makeover.
The best measure of documentation quality isn't whether it's comprehensive, but whether someone new can become productive without repeatedly asking "why?"
Track these as documentation smells:
Same questions appearing in multiple PR reviews
New engineers needing extensive 1-on-1 context sessions
Repeated digging through Git history to understand decisions
If your new hires spend the first two weeks just understanding why things are the way they are, your documentation is not nearly efficient.
The Bus Factor
The Bus Factor measures how many team members, if suddenly absent (hit by a bus), would cause the project to stall due to lost knowledge.
If only one person knows why a critical piece of code exists, you have a critical knowledge capture problem.
Example of documented knowledge that survives personnel changes:
// CONTEXT: This throttle delay was determined through A/B testing in Q3 2023.
// Reducing it below 300ms caused a 15% increase in failed payments.
// The failure pattern suggested race conditions in payment gateway's
// deduplication logic, but they couldn't confirm.
// Full experiment details: ADR: docs/decisions/003-payment-throttle.md
This preserves the critical constraint, explains why it matters, and points to deeper context without relying on any single person.
Things You Should Document
To make sure your documentation is really relevant for future maintenance of the system, these are some of the things code cannot preserve, but are important to capture:
1. Intent (Why This Exists)
Intent answers the question: Why does this code exist at all?
Example:
// This exists to prevent duplicate API calls during route transitions.
// Removing this reintroduces a race condition seen in production (INC-1847).
Without this, future changes risk "simplifying" away something essential.
2. Constraints (What Must Not Change)
Constraints are external factors shaping your code. Could be API limits, regulatory requirements, third-party system behaviours, platform limitations, etc.
// CONSTRAINT: All monetary calculations must use integer arithmetic (kobo, not naira).
// Floating-point arithmetic causes rounding errors in currency conversion.
// Our reconciliation system expects amounts in kobo (₦100.50 = 10050 kobo).
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.priceInKobo, 0);
}
Breaking them doesn't just cause bugs; it causes system-level failures.
3. Trade-offs (What Was Given Up)
Documentation should acknowledge whatever compromise was made.
// We accept higher memory usage here to reduce request latency.
// 200MB memory overhead vs 400ms average response time improvement.
// Decision made after A/B testing in Q2 2024 - see ADR-023.
Without documenting the trade-offs, someone will eventually 'optimise' your intentional decision back into the original problem you were trying to avoid.
4. Failure Modes (What Breaks If This Changes)
Basically, how a failure cascades.
// Changing this timeout breaks token refresh for mobile clients.
// Mobile apps cache the refresh interval and expect this exact timing.
// Reducing it causes auth loops. Increasing it causes session expiry.
Failure modes turn fear into informed caution.
5. Alternatives Considered
Sometimes it might seem like there is an obviously better solution than the one implemented. This explains why the implemented solution works better for your unique context.
// Considered using Redis for this cache, but:
// - Adds infrastructure dependency for <1000 items
// - Network latency worse than in-memory for our access pattern
// - Cache invalidation is simpler with process-bound state
// We'll revisit when item count exceeds 5000 (currently ~800).
What NOT to Document
Documentation requires maintenance, and every word you write must be kept accurate. Do not document:
Self-evident code
// BAD: Stating the obvious
// This function calculates the total price
function calculateTotalPrice(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
Obvious comments train engineers to ignore all comments, and eventually, the important ones. If the code is clear, silence is better than noise.
Temporary implementations
If something is marked for deletion, extensive documentation is a waste. A simple// TODO: Replace with new auth system in Q2 suffices.
Timestamp Trail
// Updated by: John, 15/03/2025
// Updated by: Sarah, 22/05/2025
Git already tracks who changed what. Document why it changed:
// Originally built for single currency support.
// Extended for multi-currency in May 2024 (ADR-015).
Tests that Documents
The test names should immediately tell what the system does or what it doesn’t do without reading a single line of implementation.
it("rejects payments when user's daily limit is exceeded, even if individual transaction is valid", () => {});
it("never charges user twice even if API call is retried", () => {});
Future engineers know the expected behaviour before they touch any code.
AI-proofing your Documentation
While coding agents can read and refactor code brilliantly, they gradually destroy the context that makes code maintainable.
An AI assistant sees this:
await new Promise(resolve => setTimeout(resolve, 100));
It suggests: "This arbitrary delay can be removed for better performance."
The AI doesn't know:
That this was discovered through a production incident
That the delay is empirically derived, not arbitrary
That "optimising" this away reintroduces a bug
To achieve a well-executed AI-assisted refactoring, constraints, if not explicitly stated, will be "improved" away.
Make constraints explicit and assertive:
// DO NOT REMOVE: This delay is required, not optional.
// DO NOT REDUCE: 100ms is the empirically tested minimum.
// Removing this reintroduces production bug INC-2847.
await new Promise(resolve => setTimeout(resolve, 100));
Use language that AI models recognise as non-negotiable. Weak language like "might cause issues" gets ignored. Strong language like "DO NOT" and "REQUIRED" has a better chance of surviving AI suggestions.
What AI Can and Cannot Do
While documentation can be automated, AI agents are excellent at explaining structure, summarising flow, and generating boilerplate documentation. But lag in capturing intent, judgment, and product context surrounding a decision.
Agentic automation can keep documentation in sync, but humans still ought to provide the nuanced details that add real relevancy to documentation.
Documentation that survives isn't about being comprehensive. It's about being relevant.
It captures what code cannot: intent, constraints, trade-offs, and failure modes. It lives close to the code. It gets updated as part of normal development. It's enforced in code reviews. It's treated as a first-class engineering practice.
Documentation debt becomes evident the moment it becomes easier to ask someone.


