AlteredCraft · Case study

Recalibrating build-versus-borrow when code is cheap to write

One Flask todo app, built twice. Replacing its heavy dependencies with small, AI-written, purpose-built code cut the third-party production footprint from 216,727 to 32,654 lines of code (LOC), an 85% reduction. A shared test suite held behavior identical across both builds, and the security-sensitive libraries stayed with the experts.

Measured 2026-07-15 with cloc 2.06, production scope (code that ships to prod; dev/test toolchain excluded).[1] Full method, per-step diff trail, and the argued stopping point in research notes.

Third-party prod code, baseline
216,727
LOC across 649 files
After the removals
32,654
−184,073  (−85%)
Dependency lines shed per line written
409×
184,073 shed for 450 written
Declared runtime deps
5 → 1
only flask remains

The premise

What changed is the cost of writing the alternative

When code was expensive to write, pulling in a general-purpose library was usually the rational choice. You got a working solution cheaply, and accepted the costs: unknown maintainers, often far more code than you need, and a "good enough" fit to your actual problem. When AI can produce a narrow implementation on demand, and a test suite can hold it in place, it's worth asking dependency by dependency whether it is worth the trade-off to borrow versus build. For some, owning a small, purpose-built module you can read end to end turns out to be the better deal. How many is a dial, not a doctrine. Your app, your team, and your risk profile set it.

The example web app is a Python full-featured Flask + SQLite + login todo app, kept in two behavior-identical variants: a baseline on the conventional Python web stack, and a limited variant whose heavy dependencies were replaced one at a time. A shared black-box test suite, byte-identical across both, is the regression net each swap had to pass.[2] Behavior is held constant, so the only difference left to measure is how much third-party code each variant ships.

Screenshot of the todo app: a My Tasks page with search, All/Active/Done filters, priority and category dropdowns, sorting, a 'Task added' flash message, and one task tagged HIGH priority in a Podcasts category with a due date.
The app in question: accounts, per-user task lists, priorities, categories, due dates, search, filtering, sorting. Both variants serve this exact UI. The experiment only counts if nothing here changes.

The headline

Every number on this page comes from cloc, an open-source line counter. Point it at a directory and it walks the tree, works out each file's language, and reports blank, comment, and code lines separately. The figures here are the code column only, counted against a production-only virtualenv built from the lockfile.

Third-party production code, before and after

Lines of code that ship to production, excluding the dev/test toolchain. The same app, the same tests passing, 85% less dependency code underneath it.

Before and after third-party LOC 0 50k 100k 150k 200k Baseline: 216,727 LOC Limited variant: 32,654 LOC Baseline Limited 216,727 32,654 85% smaller footprint

Where the weight was, and how it came off

Two features carried ~80% of the footprint

SQLAlchemy alone was 61.8% of all shipped dependency code: 133,937 lines for an app with two tables and a dozen simple queries. Email validation cost another ~39k: the 768-line email-validator drags in an entire IDNA + DNS stack (idna 19,389 and dnspython 19,300) to format-check a string. Together, the ORM and the email stack are 80% of everything that ships. In this app, the framework glue was never the bloat. The engines were.

What five lines in pyproject.toml actually install

Left: the dependency list as a developer sees it. Right: the 16 packages it resolves to in production, sized by lines of code. Line thickness tracks each declaration's share of the footprint.

What five lines in pyproject.toml actually install The five pyproject.toml entries resolve to 16 packages totaling 216,727 lines. flask-sqlalchemy pulls in 137,490 lines, dominated by SQLAlchemy at 133,937 (61.8% of the footprint); email-validator pulls in 39,457 including idna 19,389 and dnspython 19,300; flask itself accounts for 32,596 across seven packages led by werkzeug 11,986; flask-wtf pulls in 6,443 and flask-login 683. WHAT YOU DECLARE WHAT ACTUALLY INSTALLS pyproject.toml dependencies = [ "email-validator>=2.3.0", "flask>=3.1.3", "flask-login>=0.6.3", "flask-sqlalchemy>=3.1.1", "flask-wtf>=1.3.0", ] 39,457 LOC · 3 packages · 18.2% idna — 19,389 LOC · internationalized domain names — 8.9% of the footprint, pulled in transitively idna 19,389 dnspython — 19,300 LOC · DNS toolkit for deliverability checks — 8.9%, pulled in transitively dnspython 19,300 email_validator — 768 LOC · the validator itself — 0.4%; the two packages above are its freight email_validator 768 32,596 LOC · 7 packages · 15.0% werkzeug — 11,986 LOC · WSGI utils + werkzeug.security password hashing — 5.5% werkzeug 11,986 jinja2 — 8,557 LOC · templating; autoescape is the XSS defense — 3.9% jinja2 8,557 click — 6,673 LOC · the flask CLI — 3.1% click 6,673 flask — 4,068 LOC · the framework proper — 1.9% flask 4,068 itsdangerous — 650 LOC · signs the session cookie — 0.3% itsdangerous 650 markupsafe — 394 LOC · HTML escaping — 0.2% markupsafe 394 blinker — 268 LOC · signals — 0.1% blinker 268 683 LOC · 1 package · 0.3% flask_login — 683 LOC · session/auth management — 0.3% flask_login 683 137,490 LOC · 3 packages · 63.4% sqlalchemy — 133,937 LOC · ORM + Core — 61.8% of all shipped dependency code, for two tables and a dozen queries sqlalchemy 133,937 61.8% of the whole footprint typing_extensions — 2,549 LOC · back-ported typing — 1.2%, pulled in by SQLAlchemy typing_extensions 2,549 flask_sqlalchemy — 1,004 LOC · Flask ↔ SQLAlchemy glue — 0.5% flask_sqlalchemy 1,004 6,443 LOC · 2 packages · 3.0% wtforms — 5,923 LOC · form definitions + validation — 2.7%, pulled in transitively wtforms 5,923 flask_wtf — 520 LOC · CSRF + Flask/WTForms glue — 0.2% flask_wtf 520 5 declared dependencies 16 installed packages 216,727 LOC shipping to production
Per-package LOC from the same production-scoped cloc run. The 16 packages sum to 216,669; 58 LOC of stray vendored files, attributable to no single package, complete the 216,727 baseline.
The gray line

Email validation is where this gets genuinely arguable

Email validation is the cut I'd call a judgment call rather than a clear win. What's legal per the RFC grammar is far broader than what most apps want to accept, so shedding the ~39k-line stack hands you the job of drawing that line. It cuts both ways: the limited variant went deliberately narrower than the spec, then quietly accepted all-numeric TLDs like user@example.123 until the shared suite caught it.

Owning a small validator buys precise control, and cheap code includes cheap tests. If you'd rather not carry that, the expert-maintained library remains a good answer.

One commit per dependency, the suite green after each

Each removal replaced one library with purpose-built first-party code and landed as a single git commit. A step only counted when the unchanged test suite passed afterward. Plotted as a running total, the descent belongs almost entirely to those two engines: the last three steps together shed about 4% of what the first two did. By then, diminishing returns made stopping the easier case to argue.

Third-party LOC still shipping after each removal

Running total after the dependency named below each column was replaced. The final column (green) is the shipped limited variant.

Third-party LOC remaining after each removal step 216,727 at baseline; 79,237 after SQLAlchemy; 39,780 after email-validator; 39,260 after Flask-WTF; 33,337 after WTForms; 32,654 after Flask-Login. The two largest drops are annotated: −137,490 for the ORM and −39,457 for the email stack. 0 50,000 100,000 150,000 200,000 Baseline — 216,727 LOC After removing SQLAlchemy — 79,237 LOC (−137,490) After removing email-validator — 39,780 LOC (−39,457) After removing Flask-WTF — 39,260 LOC (−520) After removing WTForms — 33,337 LOC (−5,923) After removing Flask-Login — 32,654 LOC (−683) · shipped final state 216,727 79,237 39,780 39,260 33,337 32,654 Baseline − SQLAlchemy − email-val. − Flask-WTF − WTForms − Flask-Login −137,490 −39,457 the ORM the email stack

Where the cutting stopped

The 32,654 lines that stay, and why

The stopping point deserves as much attention as the cuts. The one declared dependency left is flask; everything still shipping is Flask's own transitive core. Most of it is the kind of code I'd leave with the experts: password hashing (werkzeug.security, required anyway), autoescaping as the XSS defense on every template, and cookie signing that the first-party CSRF and remember-me modules deliberately ride on rather than reinvent. In this experiment, AI-written replacement earned its keep against oversized general-purpose engines whose behavior a test suite can pin down. That is a much narrower claim than rewriting the platform or the cryptography.[3] The numbers here show what each swap cost and saved. How far to take it in your app is your call.

What one line in pyproject.toml still installs

The limited variant's entire dependency list. It resolves to Flask's own transitive core: seven packages, 32,596 LOC, none of it rewritten. Bars are on the same scale as the baseline figure above.

What one line in pyproject.toml still installs The limited variant declares only flask, which resolves to seven packages totaling 32,596 lines: werkzeug 11,986; jinja2 8,557; click 6,673; flask 4,068; itsdangerous 650; markupsafe 394; blinker 268. That is 15% of the baseline footprint. WHAT YOU DECLARE WHAT STILL INSTALLS pyproject.toml dependencies = [ "flask>=3.1.3", ] 32,596 LOC · 7 packages · −85% vs baseline werkzeug — 11,986 LOC · WSGI utils + werkzeug.security password hashing — required anyway werkzeug 11,986 password hashing — the KEEP jinja2 — 8,557 LOC · templating jinja2 8,557 autoescape = the XSS defense click — 6,673 LOC · CLI framework click 6,673 the flask CLI flask — 4,068 LOC · the web framework flask 4,068 the framework proper itsdangerous — 650 LOC · cryptographic signing itsdangerous 650 signs the session cookie markupsafe — 394 LOC · safe string handling markupsafe 394 HTML escaping blinker — 268 LOC · signal/event dispatch blinker 268 signals 1 declared dependency 7 installed packages 32,596 LOC shipping to production
Same production-scoped cloc run. The 58 LOC of stray vendored files noted above ride along with these seven packages, bringing the shipped total to 32,654.

The record

Removal log: what each step shed and cost

Every number below comes from re-running the same production-scoped cloc measurement after each commit. ~409 lines of dependency code shed for every first-party line added. The 450 lines added are small enough to read in one sitting. The 184,073 they replaced were not.

Third-party (3P) and first-party (1P) production LOC in the limited variant, per step.
StepDependency removed3P shed1P added 3P total1P total (app/)
0— (baseline clone)216,727964
1SQLAlchemy, Flask-SQLAlchemy (+ typing_extensions)137,490+10079,2371,064
2email-validator (+ idna, dnspython)39,457+2139,7801,085
3Flask-WTF520+3939,2601,124
4WTForms (+ i18n catalogs)5,923+19533,3371,319
5Flask-Login683+9532,6541,414
Total184,073+45032,6541,414

Side story: the same trade exists in dev tooling. python-dotenv (766 LOC, local-only .env loading; prod injects real env vars) is a dev dependency here, and the limited variant still swapped it for a 12-line first-party loader. Dev deps can shrink too, though selectively. I'm not about to replace pytest.

What I'd take from this

Recalibrate the default, don't abandon it

None of this says third-party dependencies were a mistake. Flask is still here, and so is every library doing work I'd rather not own. What moved is the cost of the alternative: "add the dependency" used to be the cheap answer almost by definition, and for a few of these it stopped being. The 85% came from two decisions rather than from discipline. An ORM for two tables, and an RFC-complete validator for a format check.

The practical version is smaller than the headline. Look at your own footprint the way cloc sees it, find the general-purpose engines you're using a sliver of, and price the purpose-built version now that writing it is cheap. A few will be worth owning. Most won't be, and the ones holding your passwords and your session cookies are the last place I'd start.

The part I'd stress hardest is the tests. Every removal here was safe because a byte-identical black-box suite, cheap to write, could tell me the app still behaved the same afterward. Without that regression net, you have no evidence a swap preserved behavior. Without it, you're replacing working code and finding out in production.

This is one small app, measured once. A larger team, an older codebase, or compliance requirements I don't have would each argue for keeping more of the stack. My claim is narrow: writing the replacement yourself got cheap, so the build-versus-borrow call is worth making again, one dependency at a time.