The Rule of Three for Shared Code Across a Portfolio
When to extract shared code into your foundation: wait for the third use. Premature abstraction costs more than duplication in a portfolio of products.
Do not move code into your shared foundation the first time you need it in two places. Wait until the third. The first use teaches you nothing about what varies. The second use is a coincidence. The third use is a pattern, and only then do you know enough to draw the abstraction where it actually belongs. I run about twenty products on one foundation, and the fastest way to poison that foundation is to promote code before you understand it.
When should I extract shared code into the foundation?
The rule of three is simple. Copy the code. Then copy it again. On the third product that needs it, stop and extract. By then you have three real call sites, and the differences between them show you the seams. You know which parts are truly shared and which parts each product bends to its own shape.
Extracting on the second use is a trap. Two examples look identical until the third arrives and blows the interface apart. Now you own an abstraction built for a world that no longer exists, plus a migration to fix it across every consumer. Duplication is cheap to fix later. A wrong abstraction is expensive to fix forever, because every product now depends on its shape.
This is not a license to copy-paste forever. It is a rule about timing. Duplication is a debt you can pay off any afternoon. A bad shared interface is a debt that compounds, which is the opposite of what a foundation that compounds is supposed to do.
Why premature abstraction is worse than duplication
An abstraction is a promise. It says: these three call sites are the same thing, and they will stay the same. When that promise breaks, you get the worst code in the codebase. Conditionals pile up inside the shared function to handle each caller's special case. Boolean flags multiply. The function that was supposed to remove duplication now encodes every difference it was meant to hide.
I have seen a shared "send notification" helper grow eleven parameters because it was extracted after the second product. Each new product added a flag. Nobody could change it without checking all eleven consumers. That is not reuse. That is a shared liability wearing a helper's clothes. The mistakes that break a shared foundation almost always start here, with an eager extraction.
Duplication has an honest cost you can see: two copies, two edits. Premature abstraction hides its cost inside coupling you discover later, usually at the worst time.
How the rule works in a portfolio, not just one app
Inside a single app, the rule of three is a nicety. Across a portfolio it is survival. When I promote something into the foundation, every current and future product inherits it. The blast radius is the whole portfolio. So the bar for promotion has to be higher than it would be inside one codebase.
Here is the test I use before anything moves into the shared layer:
- Three real products use it, not two products and a hypothetical.
- The interface has survived at least one change without needing new flags.
- I can name what varies between callers and it lives in the caller, not the shared code.
- Removing it from a single product would be strange, not just inconvenient.
If it fails any of those, it stays as duplicated code in each product for now. That decision costs me a little copy-paste and buys me the right to abstract later with real information. This is the same discipline behind knowing what belongs in a shared foundation at all. Not everything that repeats deserves to be shared. Volatile code that changes per product should stay local, even if it looks identical today.
The tooling should make both paths cheap. A good build platform lets you copy a pattern into a new product in minutes and lets you promote it into the shared layer later without a rewrite. I lean on Bootspring to keep that promotion path smooth, so the rule of three stays a judgment call about design and not a fight with the plumbing.
The exception: infrastructure you own from day one
The rule of three governs product logic. It does not govern the base layer. Auth, billing, the deploy pipeline, the database access pattern: those are shared from the first product because they are not guesses. I already know every product will authenticate users and take money. You standardize those once and never look back.
The line is knowledge. Where you already know the shape, share immediately. Where you are still learning the shape, copy until the third product tells you the truth. Get that line right and your foundation stays a place code earns its way into, not a dumping ground it falls into.