Part 1 of a series on software architecture for engineers at every stage — from beginners building their first production service to staff architects managing enterprise systems.
The Question Nobody Asks Out Loud
There is a question that surfaces in every engineering team, in every company, in every codebase larger than a weekend project:
"Where does this go?"
Not where should it go in theory. Where does it actually belong right now, given the directories that already exist, the conventions established by engineers who left eighteen months ago, and the unspoken rules everyone follows until a new hire touches the wrong file?
A few years ago, a senior engineer joined Slack's iOS team, stepping into a codebase containing over 13,000 files spread across 27 top-level directories. That engineer spent their first three months not shipping features, but building a map. Not a literal document, but a fragile mental model of where business logic hid, which directories were actively maintained versus silently abandoned, and which services had undocumented edge cases everyone worked around by instinct.
Three months. Full salary. Fraction of an output.
Industry data shows that a senior engineer earning $180,000 annually requires roughly six months to reach full productivity in a mid-to-large production system. That translates to a $90,000 onboarding tax per hire before an organization sees a net-positive return.
flowchart TD
subgraph QUESTIONS["The Three Questions Every New Hire Asks"]
direction TB
Q1["1. Where does this type of thing live?"]
Q2["2. Who owns this module?"]
Q3["3. Why was this decision made?"]
end
subgraph OUTCOME["Two Architectural Realities"]
direction TB
A1["Self-Serve Codebase: Answered passively by clear directory boundaries"]
A2["Structural Chaos: Answered in Slack 50x/week ($90,000 onboarding tax)"]
end
QUESTIONS ==>|"Architecture determines the answer"| OUTCOME
classDef qClass fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#f8fafc;
classDef goodClass fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#f8fafc;
classDef badClass fill:#450a0a,stroke:#ef4444,stroke-width:2px,color:#f8fafc;
class Q1,Q2,Q3 qClass;
class A1 goodClass;
class A2 badClass;
Research from Sourcegraph confirms that high-velocity teams do not have better documentation. They have self-serve codebases where new hires can answer their own questions simply by navigating the tree.
"The 'where do I put this file' question is not a junior engineer problem. It is a system design failure that the structure never answered."
Structure Is Not Aesthetic. It Is Communication.
The debate about folder organization is usually framed as cosmetic: layer-based versus feature-based, domain-driven versus technical separation, shallow trees versus deeply nested packages.
They are not matters of taste. They are matters of communication.
Every directory created in a repository creates a default answer to a question that will be asked hundreds of times over the life of the system:
- A
services/folder communicates that logic is classified by technical mechanism rather than business capability. - A
shared/utils/directory communicates that its contents belong to everyone, which in production means they belong to no one. - A
legacy/directory communicates that its code should not be touched—until a priority feature requires modifying it, and nobody knows what safe modification looks like.
Structure creates defaults. Defaults become patterns. Patterns harden into load-bearing walls in the architecture of a team's understanding.
flowchart TD
A["PR 1: I don't know where this helper goes"] --> B["Compromise: Dump into /shared/utils/"]
B --> C["Month 6: 61 unrelated files accumulated"]
C --> D["The Blast Radius Trap: Imported by 40 services, unmaintained by all"]
classDef step fill:#1e293b,stroke:#f59e0b,stroke-width:2px,color:#f8fafc;
classDef alert fill:#450a0a,stroke:#ef4444,stroke-width:2px,color:#f8fafc;
class A,B,C step;
class D alert;
As one engineer described the inevitable surrender: "The PR merged eventually. The file went into utils/ because everyone got tired." Six months later, that directory contained sixty-one unrelated files, zero coherent organization, and a reputation as the graveyard where code goes to retire.
Ward Cunningham introduced the technical debt metaphor in 1992 (later formalized by Martin Fowler): taking shortcuts is like borrowing money—you pay compounding interest until the principal is repaid. Stripe's landmark global research study, The Developer Coefficient, found that engineers spend over 33% of their working hours wrestling with technical debt and bad code—costing the global economy an estimated $300 billion in lost productivity annually.
Structural debt presents as five-minute debates happening fifty times a week, across a team of twelve engineers, sustained over three years.
Conway's Law: Why This Is Structurally Inevitable
To understand why codebase structures drift into disarray, look at the humans building them.
If an engineering organization consists of three separate teams—frontend, backend, and DBA—Conway's Law dictates they will build a three-tier architecture: presentation, API, and database layers. Not because an architect designed it, but because that is how the humans talk to each other.
If you instead reorganize those same engineers into cross-functional squads—checkout, payments, and inventory—the codebase splits into three domain services.
flowchart TD
subgraph ORG["Organizational Communication Graph"]
direction TB
F["Frontend Squad (Floor 2)"] <--> B["Backend Squad (Floor 3)"]
B <--> D["DBA / Data Squad (Separate Dept)"]
F -.-|"Rarely communicates directly"| D
end
subgraph CODE["Inevitable Codebase Architecture"]
direction TB
UI["/frontend/ - Presentation Layer"] --> API["/api/ - Application Layer"]
API --> DB["/database/ - Persistence Layer"]
UI -.-|"Coupling friction emerges here"| DB
end
ORG ==>|"Conway's Law: Org Chart rotated 90 degrees"| CODE
classDef orgClass fill:#1e293b,stroke:#3b82f6,stroke-width:2px,color:#f8fafc;
classDef codeClass fill:#0f172a,stroke:#10b981,stroke-width:2px,color:#f8fafc;
class F,B,D orgClass;
class UI,API,DB codeClass;
In 1967, Melvin Conway submitted a paper to Harvard Business Review stating: any organization that designs a system will produce a design whose structure mirrors the organization's communication patterns. MIT and Harvard Business School tested this empirically, proving that loosely coupled organizations produce significantly more modular architectures than tightly coupled ones.
"Team assignments are the first draft of the architecture." — Michael Nygard
"If the architecture of the system and the architecture of the organization are at odds, the architecture of the organization wins." — Ruth Malan
flowchart TD
A["1. Target Architecture: Define decoupled domain boundaries"]
--> B["2. Reshape Team Topology: Small, autonomous two-pizza squads"]
--> C["3. Code Follows Boundaries: Directories and APIs mirror team ownership"]
classDef step fill:#1e293b,stroke:#8b5cf6,stroke-width:2px,color:#f8fafc;
class A,B,C step;
This is the Inverse Conway Maneuver, formalized in Team Topologies. Jeff Bezos' famous two-pizza team rule was not an HR policy about meetings. By giving small teams end-to-end ownership of a single service, the team boundary dictated the service boundary, the service boundary dictated the API, and the directory layout followed.
Bezos didn't redesign the codebase. He redesigned who talked to whom.
Every Folder Has a Story: Three Landmark Disasters
Structural decay rarely begins with negligence. It begins with local convenience. But when ambiguous structure meets production pressure, the outcome shifts from friction to catastrophe.
flowchart TD
subgraph K["1. Knight Capital 2012 - Acute Dead Code Disaster"]
direction TB
K1["Root Cause: Zombie Power Peg code dormant for 9 years"]
K2["Trigger: 1 of 8 servers missed deploy; reused dormant flag"]
K3["Impact: $460M lost in 45 minutes; 4M unintended orders; firm collapsed"]
K1 --> K2 --> K3
end
subgraph G["2. GitLab 2017 - Ambiguous Environment Guardrails"]
direction TB
G1["Root Cause: Identical directory paths across primary and backup"]
G2["Trigger: Fatigued engineer ran wipe command in wrong terminal tab"]
G3["Impact: 300 GB deleted; all 5 backup mechanisms failed live"]
G1 --> G2 --> G3
end
subgraph S["3. Apple Siri 2011-2024 - The 13-Year Slow Compound"]
direction TB
S1["Root Cause: 13 years of intent heuristics patched over rules"]
S2["Trigger: Fragile architectural debt blocked modern LLM integration"]
S3["Impact: Core reliability fell below 80%; $1B/yr paid to Google for Gemini"]
S1 --> S2 --> S3
end
K ==> G ==> S
classDef disaster fill:#1e1e2e,stroke:#ef4444,stroke-width:2px,color:#f8fafc;
class K1,K2,K3,G1,G2,G3,S1,S2,S3 disaster;
Knight Capital (2012): Reused a configuration flag that had activated "Power Peg"—a testing function deprecated in 2003 whose code was never excised. One server missed deployment, ran the old binary, and executed four million unintended orders. Within 45 minutes, Knight Capital lost $460 million. The SEC administrative proceeding confirmed the cause: dead code left lingering in an ambiguous directory.
GitLab (2017): A fatigued database engineer troubleshooting replication lag had multiple terminal tabs open. Due to identical directory paths, he ran a wipe command on the primary server instead of the secondary replica. Three hundred gigabytes of production data vanished. All five backup systems failed in live recovery conditions. GitLab survived from a 6-hour manual snapshot and published their landmark postmortem.
Apple Siri (2011–2024): Over thirteen years, engineers patched heuristics onto legacy rule engines until core feature reliability dropped below 80%. In 2025, Craig Federighi confirmed that merging the legacy system with modern LLMs had failed; the 2011 architecture had to be scrapped. Apple agreed to pay Google an estimated $1 billion annually for Gemini models, while its AI leadership was restructured.
"Structure doesn't fail loudly. It fails slowly, in incidents, forgotten decisions, and new hires who stop asking questions."
Structure Changes When Forcing Functions Hit
Engineering teams almost never refactor because clean code is virtuous. They refactor when an external forcing function arrives—an event that makes the cost of living with the broken structure exceed the cost of tearing it apart.
sequenceDiagram
autonumber
participant Event as External Shock
participant System as Fragile System
participant Action as Forcing Function
participant Future as Resilient Architecture
Note over Event,System: Case 1 - Twitter 2010 FIFA World Cup
Event->>System: World Cup Goal TPS Spike
System-->>Event: Fail Whale appears across web
System->>Action: Public embarrassment - 3-year migration off Monorail
Action->>Future: Decoupled SOA - Scala JVM Finagle and Zipkin
Note over Event,System: Case 2 - Amazon 2002 Bezos Memo
Event->>System: Shared DB dependencies paralyze delivery speed
System->>Action: CEO Mandate - Expose service interfaces or be fired
Action->>Future: Hardened internal services become AWS platform
During the 2010 FIFA World Cup, Twitter was running on a monolithic Ruby on Rails application called the Monorail. Every goal scored in South Africa brought the site down. That public embarrassment forced a three-year migration to Scala and the JVM, producing distributed primitives like Finagle and Zipkin.
Around 2002, Amazon experienced an internal forcing function. Cross-team dependencies and shared database access had ground delivery to a halt. Jeff Bezos issued his legendary mandate:
"All teams will henceforth expose their data and functionality through service interfaces. Teams must communicate with each other through these interfaces... Anyone who doesn't do this will be fired. Thank you; have a nice day."
That memo forced the decoupling that accidentally built Amazon Web Services (AWS). The forcing function was not a crash. It was a memo.
flowchart TD
T1["1. The 2nd Team Joins - Implicit mental models break when team expands past 5 engineers"]
T2["2. The Auditor Arrives - SOC 2 and GDPR mandate customer data isolation from shared folders"]
T3["3. The 2 AM On-Call Page - An engineer who didn't write the code must triage an outage in minutes"]
T1 --> T2 --> T3
classDef trig fill:#1e293b,stroke:#eab308,stroke-width:2px,color:#f8fafc;
class T1,T2,T3 trig;
The Running Case Study: The Fintech Engine
To keep architectural concepts grounded, this series introduces a running case study: a production fintech backend.
flowchart TD
subgraph INTAKE["Customer and Payment Flow"]
direction LR
AUTH["/auth/ - Customer Onboarding - JWT and Session Tokens"]
TXN["/transactions/ - Payment Processing - Gateways and Cards"]
AUTH --> TXN
end
subgraph CORE["Ledger Invariants and Events"]
direction LR
LEDGER["/ledger/ - Double-Entry Ledger - Balance Invariants"]
HOOKS["/webhooks/ - Event Dispatch - Merchant Webhooks"]
end
TXN ==>|"Guaranteed balance debit"| LEDGER
TXN -->|"Async payment event"| HOOKS
classDef ft fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#f8fafc;
class AUTH,LEDGER,TXN,HOOKS ft;
We will examine how to audit this repository, draw the first module boundary, prevent boundary drift, and protect critical ledger invariants from catch-all utility leakage.
A Vocabulary, Before We Go Further
Before opening the codebase in Part 2, we establish five precise terms:
flowchart TD
subgraph STABILIZERS["Architectural Stabilizers"]
CO["Cohesion - Elements change together for same business reason"]
OW["Ownership - Clear squad accountability per directory"]
end
MB["Module Boundary - Explicit dividing line between subsystems"]
subgraph FORCES["Operational Realities"]
BR["Blast Radius - Scope of damage when a component fails"]
FF["Forcing Function - External crisis forcing structural redesign"]
end
CO -->|"Strengthens"| MB
OW -->|"Enforces"| MB
MB -->|"Constrains"| BR
FF -->|"Shatters weak"| MB
classDef core fill:#1e293b,stroke:#3b82f6,stroke-width:2px,color:#f8fafc;
classDef boundary fill:#0f172a,stroke:#10b981,stroke-width:3px,color:#f8fafc;
classDef force fill:#31102e,stroke:#ec4899,stroke-width:2px,color:#f8fafc;
class CO,OW core;
class MB boundary;
class BR,FF force;
-
Blast radius — The scope of impact when a component fails. A function in
/shared/called by forty services has a massive blast radius. Blast radius is an attribute of structure, not code quality. - Module boundary — The explicit or implicit line between parts meant to evolve independently. Explicit boundaries are enforced by compilers and packages; implicit boundaries rely on goodwill and drift under pressure.
- Forcing function — An external event or mandate that makes the operational cost of maintaining the current structure exceed the cost of refactoring it.
- Ownership — Unambiguous assignment of responsibility for a module. When everyone owns a shared folder, nobody owns it.
- Cohesion — The degree to which components inside a boundary change together for the exact same business reasons.
Where This Leaves Us
The question where does this go is not a cosmetic preference. It is the primary architectural decision determining whether your codebase remains navigable or hardens into an expensive constraint.
In Part 2, we open the fintech codebase and address the first real structural challenge: how to draw the initial module boundary in a monolith that doesn't have one yet.
That decision—the placement of the very first boundary—is where software architecture actually begins.
→ Next: Part 2 — The First Cut: How Module Boundaries Get Drawn and Why They Drift
If this landed, share it with an engineer who's been in the middle of a pull request review thinking "this is wrong, but I can't say exactly why." That's the person this series is written for.
Top comments (0)