There's an old programmer's joke: the first 90% of the code takes 90% of the time, and the remaining 10% takes the other 90% of the time. I used to tell it as a joke.
Then I let AI write a whole SaaS — auth, Stripe billing, a dashboard, a public API — and the joke stopped being funny. Because AI made the first 90% genuinely instant. And the second 90% was still exactly where it had always been, waiting for me, untouched.
The demo worked in an afternoon. The product took three more weeks. This is a field report on the gap between those two things — the part no demo ever shows you.
The first 90% really is solved
Let me be fair to the machine, because the first part was astonishing.
"Set up a Next.js app with Postgres and Drizzle, auth, a billing flow, a dashboard, and a public API." It did it. Correctly. In close to one shot. The scaffolding, the CRUD, the Zod schemas, the table with sorting and pagination — the stuff I actively dislike writing — came out clean, first try, no notes. By the end of day one I had something I could click through and demo to a friend. It looked done.
That's the trap. It looked done because everything that's easy to build is also easy to show. The demo path is the happy path, and the happy path is the 90% that was never the hard part.
The rule: the speed you feel in the first afternoon is real — and it's measuring the wrong thing. "I can demo it" and "it's done" are separated by the entire job.
The last 10% isn't code. It's anticipation.
Here's what the remaining three weeks were actually made of. None of it showed up in the demo, because the demo was one well-behaved user doing exactly what I expected.
- The empty state. Day one, the dashboard has no data. The AI rendered a table with headers and a blank void underneath — technically correct, and it makes a brand-new user think the product is broken. There's no "add your first client" nudge, because I never asked for one, because in my head the table always had data in it.
- The error state. The checkout worked perfectly — with a card that approves. The first time a real card got declined, the button just… hung. No message, no retry, no path forward. The AI built the road and no guardrail, because I'd only ever described the road.
- The double-click. A user on a slow connection clicked "Subscribe" twice. Two Stripe subscriptions, one customer. The happy path fires once; real humans mash buttons.
- The race. Same record open in two tabs, both saved. Last write wins, the other silently gone. Fine in a demo where one person clicks one thing at a time.
- The 2am weirdo. A session that expired overnight, a stale dashboard left open, one click in the morning → a 401 the UI had no idea what to do with → white screen. "Handles a user doing something weird at 2am" is a different product from "handles me, awake, clicking in order."
- The public API with no armor. No rate limiting. No stable pagination. No idempotency key on the POST, so a retried request quietly created the thing twice. All the behaviors that aren't features — they're the absence of disasters.
Every one of these compiled. Every one passed the happy-path test. Not one of them was in the demo. This is the last 10%, and it's made almost entirely of things you only know to build because you've been burned by their absence.
The first 90% is construction. The last 10% is anticipation. AI is extraordinary at the first and has none of the second.
Why the AI can't close the gap
It's tempting to say "the model just isn't good enough yet." That's not what I found. The model built every single one of those fixes correctly the moment I described them. "Add an empty state." "Handle a declined card." "Debounce the submit." "Add an idempotency key." Done, done, done, done — perfectly.
The problem was never that it couldn't write the code. The problem is that it builds exactly what you describe, and the last 10% is precisely the stuff you don't think to describe. It has no scar tissue. It has never been woken at 2am by the empty state it forgot. So it will cheerfully hand you a demo and call it done, and it will be right that it's done — for the one user it imagined, who is always you, awake, behaving.
The rule: the AI closes the gap the instant you point at it. Knowing where to point is the whole job — and knowing where to point is just a list of every edge case that ever hurt you.
The uncomfortable part
So here's what actually happened to the shape of the work.
The first 90% — the part that felt like engineering, the typing, the construction, the thing that looks like progress on a screen share — collapsed to near zero. What's left is the part that never looked like much: sitting there cold, asking "what happens if they do this instead?" over and over until you've imagined every stupid, hostile, half-asleep thing a stranger will do to your software.
That part didn't get faster. If anything it got more exposed, because AI stripped away the busywork that used to disguise how much of the real job it always was. We told ourselves building software was mostly typing. It was mostly anticipating; the typing just took long enough to hide that.
And that's the senior skill. Not syntax. Not "knows the framework." The ability to look at a working demo and feel, in your gut, the nineteen ways it'll break in front of a real person. You don't get that from a model. You get it from having shipped the empty state that made someone think your product was broken, once, and never forgetting.
What I actually changed
I didn't stop using AI — I let it write 100% of the first 90%, and I'd never scaffold by hand again. I changed where I spend my attention, and how I define "done":
- "Demo works" is the start of the work, not the end. The afternoon it clicks through is day one. I now budget the real schedule around the 10%, because that's the 90%.
- I write the edge cases down before I build, not after they bite. Empty, error, loading, double-click, concurrent, expired, retried. It's a checklist now, because the model will never volunteer it and I will never remember all of it under pressure.
- The thing that writes the code is never the thing that decides it's done. A separate reviewer, prompted to break the diff — "what does a hostile, half-asleep user do here?" — catches the missing states the author waves right past, because the author's mental model is the happy path by construction.
Author writes it, a skeptic tries to break it, a human owns the call on whether it's actually finished. That separation is the only reason I can hand the typing to a machine and still ship something that survives contact with a real user. It's also how we build Xenition — our AI workspace: describe the tool you want in plain language and it builds and deploys the working app, real database and real URL. The describing is instant now. The deciding-what-to-describe is still the job, and it always will be.
Would I do it again?
In a heartbeat. The first 90% in an afternoon is not a gimmick — it's the biggest change to how I build in a decade, and I'm never giving it back.
But I'd walk in knowing what the demo is lying about. "It works" means it works for the user you imagined. The last 10% is the work of imagining the ones you didn't — and that was always the whole job. AI just took away everything that used to hide it.
Top comments (4)
Try, fail.. then Try again! If it was not for your persistence and fortitude to get it right, you may still to this day, trying to figure out what's wrong. AI takes out the initial fight but bring new macro issues not catered for. Good ready.
Keep moving forward.
The list is the useful part, because every item on it is invisible in a demo. An empty state, the error when a card is declined, a double click that creates two Stripe subscriptions, two tabs racing each other, an expired session that ends in a 401 and a white screen. None of these appear when the person building is also the only user.
The double subscription case is the one I would fix first. It costs real money, it is hard to explain to a customer, and idempotency keys solve it cleanly once someone thinks to add them. A public API with no rate limit is the same kind of gap, quiet until the day it is not.
In our fixed price work these items go into the definition of done before anything is priced, so the last 10% is part of the scope rather than a surprise three weeks long. brent alexis is right that the afternoon was real progress, it simply was not the finish line.
Good list. Here's one that got me: I tested a contact form by sending it to my own inbox, saw the message arrive and called it done. At the client's end the enquiries landed in spam.
My inbox was the happy path.
Great