Ship First or Build Foundations First? A Phased E-Commerce Example
· 9 min read · Architecture
On this page
- Both sides are right
- Designers feel the same pressure
- Which doors can you walk back through?
- Phase 1: the walking skeleton
- The screen later, the data model now
- The payment example
- Keeping technical debt visible
- Phase 2: ready for launch
- Don't open a separate phase for debt
- Checklist before Phase 1 starts
- Summary
- Sources
I've lived through the same tension at many of the companies I've worked for. Managers and clients want to see something working as soon as possible. Designers want to set up their tokens and small components first; frontend engineers want to lay the architectural foundation first. Both sides are right from where they stand. The problem is that under this pressure the foundation gets skipped and the project fills up with technical debt.
In this post I walk through how to handle that pressure, using an e-commerce project as the example.
Both sides are right
For a manager, output is the only proof that work is moving. If there's nothing on the screen, there's no work. For an engineer, the foundation is the only way to move fast later. Without it, every new page ships slower than the last.
Martin Fowler describes this in his Design Stamina Hypothesis: a project that skips design produces features faster at first. In Fowler's view, the investment in design usually pays off in weeks, not months; he adds that this is a judgment call.1 In another piece he notes that developers find poor-quality code significantly slows them down within a few weeks.2 So "foundation or output" isn't a choice. The real question is: which foundation do we build now, and which one later?
Designers feel the same pressure
The designer's urge to go "tokens first, then atoms, molecules and organisms, pages last" is a familiar one. But Brad Frost, who introduced atomic design, says plainly that it isn't a linear process: the interface is thought of as a whole and as a collection of parts at the same time.3
Dan Mall also recommends starting a design system not with abstract components but with a real piece of product, a pilot.4 So the "let's see a page right away" request isn't entirely wrong for designers either. A real page shows which components are actually needed. The right move isn't to build the foundation abstractly and apart from the page; it's to build it together with the first real page.
Every idea in this post applies to design too: token names are a one-way door, the shadow on a card is a two-way door.
Which doors can you walk back through?
To decide which foundation comes first, I use the distinction between one-way and two-way doors that Jeff Bezos laid out in Amazon's 2015 letter to shareholders: once you go through a one-way door, coming back is very hard; with a two-way door, if you don't like what you see, you can easily walk back.5
- One-way doors: The data model, the API contract, routing, folder structure, token names. Changing them later means touching the whole project. These are built right from day one.
- Two-way doors: A component's internals, a page's layout, an animation. Changing them later means touching a single file. Move through these fast and fix them later if needed.
Bezos's warning matters too: most decisions are two-way doors, and growing organizations slow down when they treat those as one-way doors as well.5 Trying to build everything perfectly up front does as much harm as building no foundation at all.
Phase 1: the walking skeleton
The first phase builds the thinnest system in which a customer can shop from start to finish. This is called a walking skeleton. In Alistair Cockburn's definition, it's a tiny implementation that performs a small function end to end and links together the main architectural components; the architecture and the features then grow on top of it together.6 So it isn't a throwaway prototype. In our example, it's code that sits on the real foundation and contains only the basic version of each screen.
For an e-commerce site, Phase 1 covers the purchase journey:
- Signing in
- Listing products
- Viewing a product's details
- Adding to cart
- Adding an address
- Paying by credit card
- Seeing the order on the My Orders page
At the end of this phase, the manager can look at the screens and say "it works." And the team got there without skipping the foundation.

The screen later, the data model now
The most common mistake when splitting work into phases is postponing a feature as a whole. Most features have two parts: the screen and the data model. The screen can wait, but if the data model isn't built right in the first phase, the second phase turns into a rewrite.
| Feature | Build in Phase 1 | Can wait for Phase 2 | If the data model waits too |
|---|---|---|---|
| Product variants | Split product and variant: price and stock live on the variant | Color and storage pickers, images that change per variant | Cart, orders and stock logic get rewritten |
| Payment | A shared payment interface plus credit cards | Apple Pay and other payment methods | Every new method touches the entire payment flow |
| Orders | An extensible structure for order statuses | Return, exchange and shipment tracking screens | Old orders have to be migrated to the new statuses |
| Price | Price calculated in one place, on the server | Coupons, campaigns and cart discounts | Cart, checkout and order summary show different totals |
| Search | Simple text search | Filters, sorting, autocomplete | Product data has to be split into filterable fields |
| Design | Semantic tokens and core components | Dark theme, micro-interactions | Every color has to be hunted down for the dark theme |
| Reviews | Nothing | Reviews and ratings | No problem: it touches nothing that exists |
The last column shows how far a feature can be postponed. Reviews can wait entirely, because adding them later only opens a new section. Variants, on the other hand, can postpone their screen but not their data model.
The payment example
The first phase can ship with credit cards only, and methods like Apple Pay can come in the second. When you leave a payment method for later, the one thing the first phase must do is not tie payments to a single method:
type PaymentMethod = "card" | "apple_pay"
type PaymentRequest = {
orderId: string
method: PaymentMethod
// An integer in the smallest unit (cents): floating-point numbers cause rounding errors in money math.
amountInCents: number
}In Phase 1 only card works. When Apple Pay arrives in Phase 2, the checkout page, the order record and the order summary don't change; a new method is simply added. It's a small decision in the first phase, but it saves you from rewriting the whole payment flow in the second.
Keeping technical debt visible
Everywhere you move fast, you take on debt on purpose. Martin Fowler calls this deliberate, prudent debt: the team knows it's taking on debt and weighs whether the payoff of an earlier release is greater than the cost of paying it back later.7 In my view, two rules are enough to keep debt prudent:
- Every debt is a ticket. The debt goes into the issue tracker, and the note in the code links to that ticket. A TODO without a link eventually becomes a comment nobody reads.
- The note says why. Not "fix this," but what was left out and why.
For example, when the variant picker is pushed to the second phase, the gallery component can carry a note like this:
// TODO(SHOP-142): Images live at the product level for now. Once the variant
// picker lands, images will be read from the selected variant.Phase 2: ready for launch
The second phase fills in the gaps from the table: the variant picker, Apple Pay, returns and exchanges, reviews, filters. If the data model was built right in the first phase, this phase is just adding screens on top of the existing structure. By the end of it, the project is ready for launch.
Don't open a separate phase for debt
It's tempting to add a third phase at the end of the project to pay off technical debt. But in my experience a separate debt phase rarely works; when the deadline gets close, it's the first thing to be cut.
Instead, debt should be spread across every phase: chip away at it a little in every sprint, at every chance, during every wait. Two habits are enough:
- Set aside a small share of every sprint for debt.
- Leave every file you touch a little cleaner.
That way, when the project ends there's no big pile of debt waiting, because it was paid off bit by bit before it could build up.
Checklist before Phase 1 starts
When you start a new project, the answers to these questions should be clear from day one. Every one of them is a one-way door:
- Are the main entities and their relationships clear? For example, product and variant, order and order line.
- Are price, stock and totals calculated in a single place?
- Is money stored as an integer, in the smallest unit?
- Is everything with a status (orders, payments, returns) built so new statuses can be added?
- Is there one consistent API contract and error format?
- Are the URL structure and routing designed to last?
- Are the folder structure and feature boundaries clear?
- Are tokens named by role rather than appearance?
- If more than one language or currency is possible, is the groundwork ready?
Everything outside this list is a two-way door: move fast, log the debt, fix it later.
Summary
- You don't choose between output and foundation; you decide which foundation comes first.
- The foundation is built together with a real page, not apart from it. That holds for designers too.
- One-way doors are built right from day one; move fast through two-way doors.
- The first phase is the thinnest system that works end to end: a walking skeleton.
- A feature's screen can wait; its data model shouldn't.
- Every debt is tied to a ticket and says why.
- Debt is paid off bit by bit in every sprint, not in a separate phase.