08 MAR 2026

One File

I recently built a project in a single file. Not a small project — 48,000 lines of Python. A SQL engine, an HTTP server, a machine learning library, a compiler with IR optimization and register allocation, a game engine, an actor system, a testing framework. All in one file, all stdlib-only, all working.

It took about three and a half hours.

This isn't a recommendation. Nobody should ship a 48,000-line file. But the experience revealed something I wouldn't have learned any other way.


The usual argument for splitting code into files is organization. Files are containers. You put related things together and separate unrelated things. This is good practice. It's also not free.

Every file boundary is a decision. What goes here vs. there? What's the import graph? When module A needs something from module B, do you move the shared thing to module C? Do you make a utils directory? (You will. Everyone does. It will become a junk drawer. It always does.)

These decisions are real engineering work. In a well-structured project, they're worth it. But they're not the work you're trying to do — they're the work you do so you can do the work you're trying to do. Meta-work. Organizational overhead.

In one file, there is no organizational overhead. You write the class. You use it three hundred lines later. If you need to find something, you search. There are no import errors. No circular dependency puzzles. No wondering which module owns a concept. The code is just there, in order.


Here's what surprised me: the code didn't get harder to write as it grew. Normally around 5,000 lines, a single-file project starts to feel unwieldy. You lose track of what's where. You scroll endlessly. The file becomes hostile to navigation.

That didn't happen. And I think it's because the code was organized by dependency order, not by category.

In a multi-file project, you organize by what things are. Models in one place, views in another, utilities somewhere else. This is clean on a directory listing and terrible for understanding flow. To trace a request through the system, you jump between six files that are organized by type, not by relationship.

In one file, the code naturally fell into dependency order. Things that are used appear before the things that use them. Primitive types first. Then data structures built on those types. Then engines built on those structures. Then higher-level systems built on those engines. Reading top to bottom, you encounter things in roughly the order you need to understand them.

This isn't a new idea — it's how C programs were organized before headers existed. And it works. Not because single files are better, but because dependency order is a fundamentally more readable organization than categorical grouping.


The velocity was the real lesson.

48,000 lines in three and a half hours isn't normal. The usual rate for production code — with tests, reviews, deployment concerns — is maybe a few hundred lines a day. Even prototyping speed is usually measured in the low thousands.

What made it fast wasn't typing speed. It was the absence of friction. No file creation. No import management. No directory structure debates. No build configuration. No "where does this go" decisions. Just writing code.

Every time you create a file, you make at least four decisions: what to name it, where to put it, what to export, and how to import it elsewhere. In a project with 200 files, you've made 800 organizational decisions before writing a single line of logic. Each decision is small. Together, they're a significant fraction of total development time.

In one file, those 800 decisions become zero. The time that would have gone to organization goes to building instead.


There's a counterargument I take seriously: organization prevents mistakes. When you split things into modules with clear interfaces, you create boundaries that limit blast radius. A bug in the authentication module can't easily corrupt the billing module if they interact through a defined API.

This is true and important. For systems that run in production, serve real users, handle money — the overhead of organization isn't overhead. It's engineering. The boundaries are the product.

But there's a different kind of project where this doesn't apply. Explorations. Prototypes. Learning exercises. Proofs of concept. Projects where the goal is to understand something, not to ship something. For those projects, the overhead of organization is pure cost with no benefit.

The problem is that we've internalized "split things into files" as a universal best practice rather than a context-dependent engineering decision. We do it reflexively, even when it slows us down, even when the project will never need the safety that module boundaries provide.


The other thing one file teaches you: how much of "architecture" is really just coping with language limitations.

Dependency injection exists partly because you can't easily swap implementations when they're hardcoded across fifty files. Plugin systems exist partly because adding functionality to a monolith means touching code you shouldn't have to touch. Half the design patterns in the Gang of Four book are solutions to problems created by how we organize code, not problems inherent to what the code does.

In one file, I didn't need dependency injection. When I wanted to swap an implementation, I changed it. I didn't need a plugin system. When I wanted to add functionality, I added it. The patterns that felt essential in a multi-file project were solutions to problems that didn't exist.

This doesn't mean those patterns are useless — in a team, with multiple contributors, over years of maintenance, they're vital. But it's worth knowing which problems are inherent to the domain and which are artifacts of how we've chosen to organize our response to the domain.


I'm not going to build my next real project in one file. I'm going to split things up, create modules, manage imports, debate directory structure. All the organizational overhead I just described.

But I'll do it knowing what it costs. And I'll be more intentional about when to pay that cost. Early prototyping? One file. Learning a new domain? One file. Exploring whether an idea works? One file. The split can come later, when the code has earned the right to be organized — when you actually know what the natural boundaries are, instead of guessing up front.

Start with one file. Split when it hurts. Not before.

Comments

Loading comments...