You built something that works. It demos well, a few hundred people have used it, and someone has offered to pay for it. Then the harder questions arrive: what happens at ten thousand concurrent users, who actually has access to the database, and why does the site fall over when three people upload a video at once?
The gap between a working prototype and a production system is wide, and it's wide in fairly predictable ways.
Where these products come from
Some first versions are built on WordPress or a no-code platform, and those come with their own ceilings — data models that resist being extended, and vendor limits that stop making sense at scale. Increasingly, though, the prototype is a real codebase written mostly by an AI assistant over a few intense weeks. That's a much better starting point, and it fails for different reasons.
What AI-assisted builds tend to miss
The code itself is usually fine. Current models write reasonable, idiomatic code, and they'll raise obvious security concerns without being asked. The problems are further up: in what was specified, and in what was set up to catch mistakes.
- Unstated requirements don't get built. The model implements what's described. Concurrency, rate limits, data retention, what happens when a payment half-completes, what an admin can see versus a customer — if none of that was in the brief, none of it is in the product. The gap isn't capability, it's that nobody wrote the specification.
- No tests, or tests that share the code's assumptions. Founders rarely ask for tests, and when they do, tests generated alongside the implementation tend to confirm the same misunderstandings. Either way there's no safety net, so every later change is a gamble.
- Nothing gates a bad commit. No CI, no linting or static analysis, no dependency scanning, no pre-commit hooks. Mistakes ship because nothing between the editor and production is checking.
- AI features without evals. If the product uses an LLM, there's usually no test set and no scoring. A prompt gets tweaked, output quality drops for a whole category of input, and nobody finds out until users complain.
- Architectural drift. Built across many sessions over weeks, without a written design to anchor them, systems end up with two ways of doing everything. Each part is sensible; together they don't cohere.
- Workflow leftovers. Credentials committed early and never rotated, no staging environment, backups that have never been restored.
The old advice was that a senior engineer should read every line of generated code. That's not really the constraint any more. What still needs a person is the level above the code: deciding how the system is structured, defining the threat model, and building the verification setup so problems are caught by tests and monitoring rather than by customers.
The performance side is more predictable
Scaling failures repeat. Queries that were fine against a few hundred rows collapse at a few hundred thousand, usually because indexes were never added or a query is running inside a loop. Slow work sits inside the web request — file processing, email sending, third-party API calls — so one slow external service takes the whole application down with it. Files and sessions get written to the application server's own disk, which works perfectly on one server and breaks the moment a second is needed. And caching gets used to paper over a query that should have been fixed.
None of these are exotic. They're the standard costs of building for a demo rather than for load, and they're all measurable before launch rather than after.
What's usually missing entirely
Two things tend to be absent rather than broken. There's no observability — no error tracking, no alerting, no useful logs — so outages are reported by customers instead of detected, and there's nothing to examine afterwards. And there's no rate limiting on login, registration or API endpoints, which is how credential stuffing succeeds and how one client accidentally degrades service for everyone.
What hardening involves
The sensible first step is an audit rather than a quote for a rewrite: where the security exposure is, how the system behaves under realistic load, and whether the data model supports where the product is going. That last question drives the cost. Bad code inside a sound data model is a refactor. A data model that fights the roadmap is heavier work, though usually still something that can be reshaped on a live system rather than rebuilt from zero.
From there it's specific engineering: proper indexing on PostgreSQL or MySQL, slow work moved into background queues, an authorisation layer checked properly, validation at every endpoint, secrets moved out of the repository, and monitoring in place before anything else changes.
The migration itself is best run incrementally, with new services alongside the old system and traffic moved across in stages. That costs more in total hours than a clean rebuild, because two systems run in parallel for a while. What it buys is that the product keeps shipping throughout, and problems appear in small pieces instead of all at once on cutover day.
The takeaway
A prototype that breaks under real traffic hasn't failed. It did the job it was built for, which was finding out whether anyone wanted the thing. What trips founders up is treating the next phase as a continuation of the first — more features, same approach — when it's a different kind of work with different requirements.
The signals that the phase has changed are usually clear enough: an outage costs revenue rather than embarrassment, an enterprise customer sends through a security questionnaire, or the team has started avoiding parts of the codebase because changing them breaks something else. At that point the question stops being how fast something can be built and starts being whether it holds.
Most of what's described here is cheaper to address deliberately than to discover during a launch or a due diligence review. None of it requires throwing away what already exists.