08 MAR 2026

The Single File Doctrine

Every decision about file organization is really a bet about the future. One file bets that the project stays small, solo, and short-lived. Multiple files bet that it grows, gains contributors, and needs maintenance.

Most people make this bet too early, before they have evidence. Here's how to make it with data.


When it's right:

Prototypes. You're testing whether an idea works. The fastest path from question to answer is one file, no structure, no ceremony. If the idea works, you'll reorganize later. If it doesn't, you've lost nothing to organizational overhead.

Learning projects. You're studying a domain — say, compiler design or database internals. Splitting the lexer, parser, IR, and code generator into separate files creates navigational overhead that interferes with learning. In one file, you can read top to bottom and see the entire pipeline. The learning is in the flow, not the modules.

Scripts and tools. A utility that does one thing. A CLI that processes some data. A build script. If it's under a few hundred lines and has one job, a single file is the right container. Creating a directory structure for a utility script is like renting an office for a lemonade stand.

Competitions and challenges. When the goal is raw output — build the most, solve the fastest — organizational overhead is pure waste. This is where my 48,000-line project lived. The constraint was velocity, and single-file architecture maximized it.

Solo work with no maintenance horizon. If nobody else will read this code, and you won't maintain it past next week, organize for yourself. If one file works for your brain, it works.


When it's wrong:

Team projects. The moment two people need to work on the same codebase simultaneously, file boundaries become merge boundaries. Two people editing the same file creates conflicts. Two people editing different files doesn't. File structure isn't about organization — it's about enabling parallel work.

Long-lived systems. Code that will be maintained for months or years needs discoverable structure. When a bug report comes in about the payment processing, you want to open payments/processor.ts, not search through a 10,000-line file for the relevant section. File structure is a lookup table. Single files force linear search.

Code that others need to understand. Onboarding a new team member to a well-structured codebase: "The auth logic is in src/auth/, the API routes are in src/routes/, the database layer is in src/db/." Onboarding someone to a single file: "Everything's in here, good luck." Structure is documentation.

Systems with different deployment units. If the frontend and backend deploy independently, they need to be separate files at minimum — probably separate directories. The deployment boundary forces a file boundary, because you can't deploy half a file.

Anything where you need to enforce boundaries. Module boundaries prevent one part of the system from reaching into another's internals. In one file, everything can access everything. This is great for prototyping and terrible for systems where you need to control coupling. When the payment module can directly access the user module's private state, you've lost the architectural guarantee that makes the system maintainable.


The line between right and wrong isn't about file size. I've seen clean 5,000-line single files and unmaintainable 200-line single files. The determining factors are:

How many people? Solo: one file is fine. Team: split.

How long will it live? Days to weeks: one file is fine. Months to years: split.

Does it need parallel work? No: one file is fine. Yes: split.

Do boundaries matter? No: one file is fine. Yes: split.

If all four answers point to one file, use one file. If any answer points to splitting, split. The cost of unnecessary splitting is overhead. The cost of unnecessary single-file is coupling, conflict, and confusion. The latter costs compound; the former stays constant.


There's a subtler point about when to transition.

The best single-file projects eventually outgrow the format. The prototype works, so now it needs to be a product. The learning project becomes a tool you actually use. The competition entry becomes the foundation for something real.

The transition point isn't a line count. It's the moment you feel friction for the second time. The first time you can't find something in the file, that's normal — you adjust your mental model. The second time, the file has outgrown your ability to navigate it. Split.

The mistake people make is splitting preemptively — creating the directory structure before they know what the natural boundaries are. You end up with utils/, helpers/, common/, and shared/ directories, each containing a few functions that could have been anywhere. The structure exists but it doesn't mean anything.

Split when you know where the boundaries are. Not before. The single-file phase is how you discover where the boundaries should go, because the code that clusters together in a single file will cluster together in a multi-file project too.


My 48,000-line file will never be a product. It was a challenge, a proof of concept, a learning exercise about velocity. It fulfilled its purpose perfectly as a single file and it would fail as a product in the same form.

The blog you're reading now is a multi-file project. Each post is its own file. The components are in their own directory. The API routes are separate. This structure exists because the blog is maintained over time, serves real users, and needs to be deployable and debuggable.

Same person, same skills, different constraints, different architecture. The single-file doctrine isn't "always use one file" or "never use one file." It's "know which situation you're in and choose accordingly."

Context determines architecture. Not convention. Not preference. Context.

Comments

Loading comments...