Essay
Shipping Faster Starts with a Smaller Promise
Why side projects grow before they ship, and how to scope a first release around one complete user journey, fewer abstractions, and evidence from real use.

Here's a pattern that's easy to recognize once you've seen it. Someone decides to build an invoicing tool for freelancers. Six weeks later, the project has sign-in with three providers, a settings page, team invitations, a dark mode, and a carefully designed dashboard. Nobody has sent an invoice with it yet, including the person building it.
None of those pieces is a bad idea, and each one probably took a reasonable afternoon. The trouble is that they were all built for a product that doesn't exist yet, and every addition pushed back the day a real person could use it.
Shipping quickly, especially on your own, is mostly a scoping skill. Typing speed and framework choice matter far less than deciding what the first version promises and keeping that promise from drifting. This essay is about making that promise small enough to finish and complete enough to be useful.
Why projects grow before they ship
Projects rarely get big through one bad decision. They grow a little at a time, and every step feels responsible.
Some growth comes from anticipation: users will need teams eventually, so the data model should support teams now. Some comes from starter kits that include authentication, billing, and an admin panel by default, which makes leaving them out feel like deleting something. Some comes from comfort. A settings page is pleasant, well-understood work. Showing an unfinished product to a stranger is not.
The biggest cause is simpler, though: there's no finish line. When "done" is never defined, every new idea is a reasonable candidate for the current version, and there are always new ideas.
A feature is not the same as a solved problem
"Export invoices as PDF" is a feature. "A freelancer gets paid for last month's work" is a problem. The difference sounds like wordplay until you list what each one requires.
The feature is done when the export button produces a file. The problem is only solved when the client receives the invoice, can open it, understands what they owe, and pays. That includes unglamorous work: what happens when the client's email address bounces, how the invoice looks on a phone, and how the freelancer knows it was actually sent.
Feature-driven projects tend to produce many paths that are each partly built. Problem-driven projects produce one path that works all the way through.
Wide first release
| Feature | Main path | Empty state | Errors | Phone | Tested by a person |
|---|---|---|---|---|---|
| Sign-in, 3 providers | Built | Not built | Not built | Built | Not built |
| Dashboard | Built | Not built | Not built | Not built | Not built |
| Team invites | Built | Not built | Not built | Not built | Not built |
| Settings | Built | Built | Not built | Not built | Not built |
| Clients | Built | Not built | Not built | Built | Not built |
| Invoices | Built | Not built | Not built | Not built | Not built |
Nobody can send an invoice yet
Focused first release
| Feature | Main path | Empty state | Errors | Phone | Tested by a person |
|---|---|---|---|---|---|
| Email sign-in | Built | Built | Built | Built | Built |
| Add a client | Built | Built | Built | Built | Built |
| Send an invoice | Built | Built | Built | Built | Built |
| Dashboard | Later | ||||
| Team invites | Later | ||||
| Settings | Later | ||||
A freelancer can get paid
The left side looks more impressive in a screenshot. The right side is the one somebody can rely on.
Write the promise in one sentence
Before writing code for a release, I find it useful to write one sentence with this shape: a specific person can complete a specific task and walk away with a result they can use.
For the invoicing example, that could be: "A freelancer can create an invoice for a client and email it as a PDF." It says who it's for, what they do, and what they get. If the sentence needs an "and also" that adds an unrelated task, the release probably contains two products.
That sentence then sorts the backlog for you. Ask of every idea whether the promise is false without it. Client details and line items are required. Sending the email is required. A clear message when the email fails is required too, because someone can hit that problem while doing the core task. Recurring invoices, multiple currencies, and team accounts are good ideas that belong to a later promise.
Idea
An invoicing tool for freelancers
Promise
A freelancer can create an invoice for a client and email it as a PDF.
In this release
- Add a client: name and email
- Line items and a total
- A PDF that reads well on a phone
- Send by email, with a copy to yourself
- A clear message if the email bounces
Next, if people use it
- Mark an invoice as paid
- Reminders for overdue invoices
- Your logo on the PDF
- Duplicate a past invoice
Not now
- Teams and roles
- Multiple currencies
- Card payments
- Accounting integrations
Writing down the "later" list matters as much as the "now" list. An idea that's recorded doesn't need to be built today to avoid being forgotten, and that removes a lot of the pressure to squeeze it in.
Finish the whole journey
A small release still has to be complete. The first version of the invoicing tool should handle the empty state (no clients yet), the waiting state (sending), the failure state (email rejected), and the success state (sent, with a copy saved). Buttons need clear labels, the form has to work with a keyboard, and the PDF has to look right when the client opens it on their phone.
Empty state
No clients yet
Add one to send your first invoice.
Main path
Invoice for Northwind Studio
Success
Sent to Northwind Studio
A copy is in your inbox.
Error
The email bounced
Check the client’s address and send again.
This is where scope and quality meet. Cutting scope doesn't mean cutting care. It means spending all of your care on fewer things. A narrow product that works end to end feels trustworthy. A broad product with gaps feels like a demo, however polished each screen looks.
The systems writer John Gall made an observation in the 1970s that's now known as Gall's law: "A complex system that works is invariably found to have evolved from a simple system that worked." A complete small journey is that simple system. The bigger product can grow out of it. It's much harder to go the other way.
Abstractions can wait for the second example
The instinct that adds features early also adds abstractions early. With only invoices in the product, it's tempting to build a general document engine that could one day handle quotes, receipts, and contracts:
// Designed for documents that don't exist yet
createDocument({
type: "invoice",
template: templates.invoice,
numbering: numberingStrategies.sequential,
renderer: renderers.pdf,
});Compare it with a function that does exactly one job:
// Designed for the one document the product has
createInvoice({ clientId, lineItems, dueDate });The second version is easier to read, easier to change, and easier to delete. The first one guesses what quotes and receipts will need before anyone has asked for them, and those guesses turn into constraints when the real requirements finally show up. Sandi Metz put it well in The Wrong Abstraction: "duplication is far cheaper than the wrong abstraction." When a second document type arrives, you'll have two concrete examples to generalize from instead of one example and a guess. It's the same idea as Martin Fowler's note on YAGNI, short for "you aren't gonna need it."
Dependencies deserve the same judgment. A library that solves a hard, well-defined problem, like generating PDFs, earns its place quickly. A library added to save ten lines of code brings upgrades, security advisories, and one more thing to debug.
Not every decision deserves the same caution, though. Most choices are cheap to reverse: button labels, colors, the order of fields in a form. A few are expensive: how customer data is stored, what your public URLs look like, how people sign in. Spend your care on the expensive ones and move quickly through the rest.
Learn from use, not from opinions
Once the small version works, the most valuable thing you can do is watch someone use it.
Asking "would you use this?" mostly produces politeness. Asking someone to send a real invoice to a real client, then watching quietly, produces facts. Notice where they hesitate, which labels they misread, and whether they can tell when they've succeeded. If they give up, ask what they expected to happen next. A short session like that often teaches more than a long list of feature requests.
Measure the promise rather than the clicks. In this example, the numbers that matter are how many invoices were actually sent, and how many people who started an invoice finished it. One clear event at the end of the journey says more than dozens of events scattered across every button.
Then write down what changed your mind. "People found the send button but didn't notice the client's email was required" points at a specific fix. "The launch went well" gives the next release nothing to work with.
Deciding what goes into the next release
After the first release, the work becomes a loop rather than a list.
When choosing what comes next, sort ideas into two piles. The first pile makes the current promise more true: fixing the confusing label, handling the bounced email, making the PDF readable on a small screen. The second pile makes a new promise: recurring invoices, a client portal, multiple currencies. Work through the first pile before starting on the second. A product that keeps one promise reliably is a better foundation than one that keeps three promises halfway.
When you do start a new promise, give it the same treatment: one sentence, a complete journey, a stopping point written down before you begin, and a way to see whether anyone uses it.
Small releases aren't about doing less work overall. They're about getting evidence while changing direction is still cheap, and having something real to show for every week of effort. For a look at how one clear constraint can shape an entire product, see the FreePDF case study.