I co-founded a company and took the technical half of it. In practice that means the backend, the inference service, the database, the web app and the tooling are mine to design, build and answer for. Somewhere around month three it stopped being a project I was working on and became a system I was responsible for, and those are different jobs.
The obvious lesson is about discipline, or time management, or some other thing that sounds good in a blog post. The real lesson is narrower and more useful: a two-person company is a hard constraint, and hard constraints are excellent editors.
You cannot afford anything clever
On a large team, an elaborate architecture has somewhere to hide. Someone else maintains the piece you over-engineered, and the cost is spread thin enough that nobody feels it. At this size, every decision bills you directly and repeatedly.
So I kept reaching for the boring option, and the boring option kept being right. One database instead of three. One deployable for the core domain instead of a service per bounded context. Module boundaries inside a single process, which give you most of the isolation benefit and none of the distributed-systems tax.
I did split one service out, and only because it earned it — a different language, a different scaling profile, and a failure mode I actively wanted contained. That is the bar I would now apply anywhere: a service boundary needs a reason you can measure, not a diagram that looks tidy.
Decide slowly about things you cannot undo
I split every decision into two buckets before making it. Expensive to reverse: the data model, the tenancy boundary, how authentication works, what the contract between two services looks like. Cheap to reverse: UI patterns, copy, prompts, anything cosmetic.
The expensive bucket got days of thought before a line was written. The cheap bucket got changed constantly and without ceremony. That split is most of what has kept the codebase workable, and it is the habit I would keep on a team of any size.
Write it down while you still remember why
I started keeping a running document of traps — things that cost me hours and would cost the next person the same. A header configuration that quietly broke three unrelated features. A code generator that fails on Windows if a dev server is holding a file open. A state selector that looked fine and looped forever.
None of that belongs in an architecture document. All of it is what someone actually needs at 11pm. Six weeks later I was that person, reading my own notes, grateful.
The rule I settled on is that documentation is part of the change, not a follow-up task. The doc gets updated in the same commit as the code, because the version that stays true is the one you are obliged to touch every time.
Tests where they buy something
I did not chase a coverage number. Coverage tells you which lines executed, not which behaviours are guaranteed, and optimising for it produces a lot of tests that assert a function was called.
- Unit tests concentrated where the logic is genuinely subtle — permission checks, billing arithmetic, anything with an off-by-one.
- A separate integration tier running against a real database in a container, for the cases where the database is the thing under test.
- A deterministic offline stand-in for the AI provider, so the whole suite runs with no network and no bill.
- Type checking as the primary gate. It catches a whole class of mistakes faster than any test I could write.
The part nobody warns you about
Owning a layer end to end means nobody is deep enough in it to tell you that you are about to do something stupid. My co-founder can challenge a product call, and does. Nobody is going to catch a bad index or a guard in the wrong order. That absence is the actual difficulty — not the workload.
What partly replaces it is writing. Explaining a decision in prose, to a reader who does not already agree with you, exposes the weak version fast. Several designs in this system died halfway through the paragraph justifying them, which is the cheapest possible place for a design to die.
Scarcity is a good editor. Not having room for anything clever is how I ended up with something maintainable.