--- title: "Shipping Faster Starts with a Smaller Promise" date: 2026-04-15 category: "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. *By Pranav Gawande. Published 2026-04-15, updated 2026-11-05. https://pranv.xyz/blog/guide-to-shipping-fast* 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. > **Figure 1.** Breadth versus completeness. The wide release has more features; only the focused one lets someone finish the task it exists for. > > Two grids compare first releases. Columns are what a feature needs to be usable: main path, empty state, error handling, works on a phone, and tested by a real person. The wide release has six features (sign-in with three providers, dashboard, team invites, settings, clients, and invoices), each with only the main path and a few scattered extras built, so nobody can send an invoice yet. The focused release has three features (email sign-in, adding a client, and creating and emailing an invoice) with every column complete, while dashboard, team invites and settings are marked as later. 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. > **Figure 2.** Narrowing an idea into a first release. The one-sentence promise decides which column each idea belongs in. > > 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 with a name and email, line items with a total, a PDF that reads well on a phone, sending by email with a copy to yourself, and a clear message if the email bounces. Next, if people use it: mark as paid, reminders for overdue invoices, your logo on the PDF, and duplicating a past invoice. Not now: teams and roles, multiple currencies, card payments, and 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. > **Figure 3.** The example invoicing tool's first release, with a designed screen for every state a person can reach, not only the happy path. > > An app window labelled as an example, showing four screens of a first release. Empty state: no clients yet, with a button to add a client. Main path: an invoice for Northwind Studio with two line items totalling 1,500 dollars and a Send as PDF button. Success: sent to Northwind Studio, with a copy in your inbox. Error: the email bounced, check the client's address and send again, with an Edit email button. 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](https://en.wikipedia.org/wiki/John_Gall_(author)): "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: ```ts // 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: ```ts // 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](https://sandimetz.com/blog/2016/1/20/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](https://martinfowler.com/bliki/Yagni.html), 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. > **Figure 4.** The release loop. Each pass either makes the current promise more true or starts a new one. > > A cycle of five steps: write the promise in one sentence; build the whole journey, including empty, loading and error states; release it to real people; watch real use and measure whether the promise was kept; decide whether to fix the current promise or start a new one, then return to the first step. 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](/blog/privacy-first-pdf-editor).