The Single-File Fallacy: Why Monolithic Codebases Sabotage Developer Onboarding
Share this article
When recreational programmer Tsoding tweeted that developers should "stop obsessing over splitting code into files," citing the 92,000-line miniaudio.h as proof single-file projects "are fine," it struck a chord with coders weary of fragmented repos. But as someone who's wrestled with monolithic codebases firsthand, I argue this extreme invites chaos—especially for engineers joining a project cold. The truth lies not in dogma, but in balancing structure with pragmatism.
The Allure and Illusion of Simplicity
Tsoding's stance resonates because excessive file-splitting is a real pain. As he notes, scavenger hunts through directories of tiny, boilerplate-heavy files (common in Java or C# ecosystems) can feel like navigating "small satellite particles." Grep becomes a lifeline regardless of structure, and when you authored the code, a single file feels intuitive—you know where everything lives. But this familiarity bias masks a critical flaw: code isn't written for its creator alone. As one developer who inherited two large single-file projects this year observed: "The mental burden was significantly greater compared to modular projects. Jumping into a large block of code without signposting is like reading a textbook without chapters."
"Reading a codebase when you already know what exists, versus when you have no idea, is a HUGE difference. It's easy to overlook this if you're the one who wrote it."
Why Structure Matters for the Uninitiated
Consider onboarding onto a backend project to build a new API endpoint. In a hierarchical setup—say, directories for db/, api/, and routes/—you immediately grasp where to start. You might implement the feature without ever touching unrelated modules. In a single file? You're forced to skim thousands of lines, holding the entire system in your head to locate insertion points. This cognitive tax isn't trivial; it delays feature development, increases error rates, and frustrates teams. Modularity acts as a map: files become landmarks that reduce mental load by contextualizing code. Dismissing this as mere "OS-level constructs" ignores how humans parse information—whether in prose or programs.
Beyond Navigation: The Hidden Benefits of Splitting
Modularity isn't just about readability; it unlocks practical advantages single-file approaches can't match:
- Version Control Harmony: Smaller files reduce merge conflicts. When five developers edit separate modules simultaneously, Git handles it gracefully. In a monolithic file, concurrent changes become collision nightmares.
- AI and Tooling Efficiency: Modern tools like LLM-powered code assistants index and analyze files independently. A 92k-line blob overwhelms context windows, while modular code allows targeted queries (e.g., "Explain the user_authentication service").
- Refactoring Resilience: Isolating components limits ripple effects. Changing a utility function in a dedicated helpers.py is safer than tweaking it inside a sprawling main.c where dependencies are invisible.
Finding the Middle Ground
The solution isn't swinging to extremes—neither Tsoding's "one file to rule them all" nor Java-style over-fragmentation. Aim for logical grouping: keep tightly related functions together (e.g., all database interactions in one file) but separate concerns when abstraction layers diverge. Tools like LSP-aware editors mitigate grep dependency by enabling symbol-based navigation, but they work best when code has clear boundaries. As projects scale beyond solo endeavors, remember that every minute saved grepping is a minute lost building. Structure isn't bureaucracy; it's the scaffold that lets teams move fast without breaking things—proving that in software, as in writing, organization transforms chaos into clarity.
Source: Against Single-File Codebases