How long does it take to build the parts every app needs?

For a competent developer starting from an empty directory, **21 to 43 working days** before a
customer can do anything useful. That covers only the core: sign-in, payments, keeping customers
separate, email, background jobs, hardening, and deployment. Add mobile apps and the pages that make
you findable and it is closer to **34 to 68 days**. This post checks those numbers against a real
codebase, area by area.

## How these numbers were measured

Every figure below comes from this codebase, counted with `wc -l` on the working tree and
`bin/rspec --dry-run` for the test count. Nothing is estimated after the fact.

Two caveats, stated up front. **Line counts include comments**, and this codebase comments heavily.
And **lines are a weak proxy for effort**: one hard twenty-line method can cost more than two
hundred easy ones. The day ranges are judgement, not measurement. The line counts are the evidence.

So read this as the shape of the work rather than a stopwatch: which parts are large, which are
small, and which are nothing like the size people assume.

## Sign-in sounds like two days

Passwordless sign-in with Google and Apple sounds like an afternoon and a gem. The repository holds
**705 lines of application code across 19 files**, and **377 lines of tests running 39 examples**.

Here is where the rest of the time goes:

- Codes expire, at ten minutes.
- Codes are single use, and failed attempts are capped at five.
- The code is never stored in plain text, only as a keyed digest, so a database leak reveals no live
  codes.
- Requests are throttled by IP address **and** by email address. Only one of those stops a targeted
  attack.
- Apple returns its callback as a cross-site form post, so the route must accept both GET and POST,
  and the client secret is a token you build and sign yourself.
- The same person can arrive as a magic code today and a Google account tomorrow. Deciding those are
  one human is a schema decision you make once and live with.

None of that is optional, and all of it is invisible in a demo.

## Payments is not the checkout

The checkout is perhaps a fifth of it. The subscription stack is **572 lines**, **225 of them the
webhook alone**, against **864 lines of tests running 80 examples**. The tests outweigh the code,
which is the correct ratio for money.

What the webhook has to survive:

- Signature verification, with a clean rejection for a malformed payload rather than a swallowed
  success.
- Duplicate delivery. The same event arriving twice must change nothing the second time.
- Never returning a server error, because providers disable endpoints that keep failing.
- Refunds, disputes, and fraud warnings, each of which has to pull access back.
- Cancellations that take effect at the end of the paid period, not immediately.
- Storing a status and a period end, not a boolean, because "paid" is not a yes or no question.

The gate that actually checks all of this before someone uses a paid feature is **21 lines**. That
ratio is the whole point: the enforcement is trivial, and the bookkeeping behind it is not. There is
more on why in [how to charge without building billing](/blog/charging-for-your-app).

## Keeping customers separate is cheap in code

This is the smallest area on the list and the most expensive to get wrong: **275 lines**, of which
only **64 are permission policy**, plus **176 lines of tests**.

It is cheap because the decision is made once, at the schema, and then followed everywhere. It is
expensive because there is no second chance. The test that matters asserts that another account's
record returns a 404, and it is worth more than the rest of the area combined. The full argument is
in [how to keep one customer from seeing another's data](/blog/keeping-customer-data-separate).

## Two areas that really are small

Email is **120 lines**, about ten of them the production mail settings, and there is no library. But
the days go to DNS records and to finding out a week later that your mail lands in spam because one
record was missing. That work shows up in no line count at all, which is exactly why estimates miss
it.

Background jobs are **145 lines** including configuration, with **104 lines of tests**. No Redis and
no extra service to run or pay for, because the queue lives in the database you already have. This
is the one area where the honest answer is that it simply got much easier.

## Hardening is a list, not a feature

**395 lines** against **383 lines of tests running 36 examples**. It is not one thing. It is six
rate limits, an enforced content policy, request filtering, and the export and deletion routes the
law expects you to have.

The content policy deserves a warning. It is enforced rather than reporting, so a missing allowance
breaks a real flow **silently**: no error, no failed request, just a button that does nothing. Ten
of those 36 examples exist only to pin that down.

## Mobile is the one people underestimate

**802 lines of native code** and **743 lines on the server**, plus **525 lines of tests running 46
examples**.

The shells themselves are small. The cost is that both stores require their own purchase system for
digital goods, so you now have a second payment path with its own receipts and notifications, and
both paths have to arrive at one answer about who is subscribed. See [getting your app into the app
stores](/blog/your-app-in-the-app-stores).

## Being findable is the largest area here

**1,552 lines**, **118 examples**. It is the biggest single area in this repository, and it is the
one nobody puts in an estimate at all.

That is the blog, the feeds, the sitemap, the structured data, the machine-readable files AI
assistants read, and generated share cards. None of it is hard. There is simply a lot of it, and
none of it exists until you write it.

## The whole thing, added up

| Area | Code | Tests | Realistic days |
|---|---|---|---|
| Sign-in | 705 | 377 | 5 to 10 |
| Payments | 572 | 864 | 5 to 10 |
| Keeping customers separate | 275 | 176 | 3 to 6 |
| Email | 120 | 47 | 2 to 4 |
| Background jobs | 145 | 104 | 1 to 3 |
| Hardening | 395 | 383 | 3 to 5 |
| Deployment and CI | 686 | n/a | 2 to 5 |
| **Core subtotal** | **2,898** | **1,951** | **21 to 43** |
| Mobile shells and in-app purchase | 1,545 | 525 | 8 to 15 |
| Blog, SEO, and share cards | 1,552 | 1,005 | 5 to 10 |
| **Total** | **5,995** | **3,481** | **34 to 68** |

Repository-wide: **5,949 lines of application code, 3,595 lines of tests, 327 examples, 12
migrations, 50 routes**. The two totals are close but not identical: the table adds native and
configuration code that lives outside the application directory, and leaves out application code
belonging to none of these areas.

Deployment is the strange row. It contains no application code at all. It is 686 lines of
configuration, and it still takes most of a week the first time.

## Why every estimate comes in low

Every feature on that list has two versions.

The demo version works. Sign-in signs you in, the checkout charges a card, the email sends. That
version genuinely is an afternoon, and it is the version people picture when they give a number.

The done version is the bulleted lists above: expiry, replay, throttling, duplicate webhooks,
refunds, the silent policy failure, the second payment path. Not one of those is optional once real
people are using the thing, and none is visible until they are.

The gap between the two versions is the estimate error. It is not a fudge factor, it is a specific
list, and it is roughly the same list in every product anyone builds. A coding agent compresses that
list meaningfully. It does not shorten it. That is the argument in [what your coding agent should
never build from scratch](/blog/what-your-agent-should-not-build).

## The part nobody estimates at all

The tests. **3,595 lines against 5,949 lines of application code**: six lines of test for every ten
of application. If an estimate did not include that, it was not an estimate for finished work. It
was an estimate for the demo version.

## The short version

- The core is **21 to 43 days**. Everything, including mobile and the content surfaces, is **34 to
  68**.
- Payments and sign-in are where the tests should outweigh the code.
- Being findable is the largest single area, and the one always left out.
- **Roughly none of it is your product.**

The money side of the same question is in [what it actually costs to start an
app](/blog/what-it-costs-to-start), and the full inventory of what is already built is in
[everything you get on day one](/blog/whats-in-the-box).
