We obsess over making code last. Maybe we should obsess over making it leave gracefully.
There's a quote that's been living rent-free in my hea...
For further actions, you may consider blocking this person and/or reporting abuse
I lived this exact lesson recently. Built three discovery paths to find upgrade authority data on the Sui blockchain. Each path was its own module, its own RPC call, its own fallback logic. Shipped with 87 tests, all green.
Then I ran 4 curl commands against live mainnet. Path A called an API endpoint that doesn't exist (DNS failure). Path B queried for blockchain events that Sui literally never emits. Path C used field names from outdated documentation. Three paths, three different flavors of wrong, all invisible in tests because I was mocking the responses I expected instead of the responses the real system returns.
Deleted all three. Replaced with one simpler path that actually works. Tool execution dropped 46% because I stopped making three RPC calls that returned nothing. The code got smaller and more honest at the same time.
The reason deletion was cheap is exactly your point about boundaries. Each path was a single function behind a shared interface. Removing one meant deleting one function and one call site. If those three paths had been woven into each other or shared state, the fix would have been a rewrite. The modularity wasn't there because I planned for deletion. It was there because I followed the pattern from the rest of the codebase. But the effect was the same: when reality disagreed with my assumptions, the cost of being wrong was three clean removals instead of surgery.
"What would it take to remove this?" is going into my review process.
This is the best real-world example I've heard. 87 passing tests, completely wrong. Oof.
But that last line is the punchline: "the cost of being wrong was three clean removals instead of surgery." That's the whole essay in one sentence. You didn't plan for deletion, but you built in a way that made it cheap anyway.
That's the secret - deletability is often just good modularity wearing different clothes.
"Deletability is often just good modularity wearing different clothes" is a great way to put it. The only thing I'd add is that smoke testing against real external systems is what reveals whether your modularity is actually real or just looks real in tests. My mocks were modular. The real system didn't care about my module boundaries. The mocks passed because I'd designed them to pass. The curl commands passed because the code was actually correct. Two very different statements.
These are the two scariest sentences about testing I've read in a while:
Modularity is necessary but not sufficient. Reality is the only review that counts.
Exactly. And the worst part is that the mocks feel like safety. You write them, the tests go green, you get the confidence boost, and you move on. The curl commands feel like extra work. But the mocks are testing your model of reality. The curl commands are testing reality. Those are different things, and the gap between them is where the bugs live.
Totally agree. I always add this to the AGENTS.md:

in mine as well ;)
out of topic, but does having line "Coding guidelines for AI agents working on this project." helps anything or is it just a nose? Don't agents already assume file called "AGENTS.md" will contain exactly that?
The idea of abstraction as a "deletion seam" instead of a scalability bet is a total game changer.
yeah, it reframes everything.
Scalability bets are guesses about the future. Deletion seams are just humility with a plan. One of those is way easier to get right.
Very good points.
We've been using plugin systems to implement these principles on a daily basis for many, many years.
The entry point of an application does only one thing: loading plugins (modules).
Every feature is a plugin and removing it means just deleting the corresponding file(s).
Here's a mini example using TOPS, for stream-oriented programming.
The router is a plugin, the home page is another, just like other pages, the navbar, the sidebar, etc. In real-life code effects are plugins, too.
Love this. "Every feature is a plugin" is exactly the bar. Delete one file, done.
Effects as plugins too - that's the hard part most systems get wrong.
Easy to delete! Perfect!
There is a level in real life where a company should be deleted from the architecture of the software system because it only plays an uncontrollable proxy role and handles critical operational data at the correspondence level. I don't think we will have the opportunity to get rid of the consequences of a decision made 7-8 years ago even next year.
I'm just a outsorced developer on that project, at the bottom of every decision chain.
Oof. That's the dark side of this whole conversation. You can design for deletability all you want, but if the organizational decision was made 8 years ago and now a company-sized knot is tied into your architecture... no amount of nice modules gets you out.
Sometimes the thing that needs deleting isn't code. It's a contract. A vendor. A "strategic partnership" no one will admit was a mistake.
That's the level where this philosophy stops being technical and starts being political.
While the goal is the same I turn the concept upside down. All code is temporary, but there is code that can become permanent.
The idea behind it is that code can become a main staple for the application. Not because nobody wants to touch it, but because with many changes it almost stays as it was when it was introduced. Only taking another direction can earmark it for removal.
Most of the times it is not because the code is robust, but because it is adaptable enough to keep providing value.
Instead of thinking about the removal, try to make the footprint as small as possible.
I do think there is power in looking at code through a deletabilty lens, but I see it more as a consequence of making code lean and mean.
One of the concepts that makes this possible is composability.
I like this reframe. "All code is temporary, but some can become permanent" — that's not a contradiction of deletability, it's the proof of it. Code that survives does so because it was deletable and just... never got deleted.
Composability as the engine makes sense too. Small, independent pieces that earn their keep.
Apparently this hits me harder than what I would have realized. I've been creating side projects and most of the time I don't think of "how to reverse this if I plan to change it again". Being intentional with having an easy save point makes so much more sense. Thank you for this insight!
glad it landed! The "save point" framing is perfect - you're not planning to fail, you're just making it cheap to change your mind.
That's all reversibility is: a save point you can actually load.
What this essay reminded me of is how much “deletability” depends on who controls the seams. In normal software, we define our own module boundaries. In cloud platforms, the seams are defined for us.
AWS gives you powerful primitives, but the boundaries they create—IAM roles, EventBridge rules, SQS/SNS wiring, CloudWatch alarms—are substrate‑level seams. Once a feature crosses those boundaries, it’s no longer just code you can delete. It’s code plus infrastructure plus permissions plus routing plus monitoring.
At that point you’re not decoupled, you’re embedded.
That’s not a criticism of AWS; it’s just the architectural reality of building on a shared substrate. The deletion cost isn’t in the Lambda function—it’s in everything around it. And that’s why “vendor lock‑in” isn’t really about fear or ideology. It’s simply the natural consequence of adopting a platform where the seams live outside your codebase.
The takeaway for me is that deletion‑oriented design matters even more in the cloud. If you don’t create your own boundaries on top of the platform’s boundaries, the platform becomes the architecture. And once that happens, reversibility gets expensive fast.
This is a crucial expansion. The essay assumed the seams are yours to control. On a cloud platform, they often aren't.
That's the real lock-in. Not malice - just death by a thousand platform attachments.
The implication is uncomfortable: maybe the most deletable system isn't the one with the cleanest modules. It's the one that avoids deep platform embrace in the first place. Or at least wraps every cross-boundary touch in an abstraction you control, not one AWS handed you.
Cloud makes creation cheap. But it makes deletion expensive in ways that don't show up in the codebase. That's a whole other essay waiting to be written.
What you said about "death by a thousand platform attachments" is exactly the seam I keep circling around. There's a whole essay hiding in that idea—not anti-cloud, just honest about how substrate-defined boundaries change the deletion calculus.
Two angles feel worth writing: platform-defined seams—how cloud primitives quietly become the architecture—and deletion-oriented cloud design—how to build tenant-scoped boundaries on top of a global substrate. They're complementary lenses, and your framing of "creation cheap, deletion expensive" is the perfect starting point.
If you ever want to take it further, the conversation has legs.
Great post! This also makes future refactoring so much cleaner. Following this philosophy, swapping or rewriting would feel way simpler and less prone to introducing bugs.
Mmm hmm. Refactoring is just deletion + addition. If deletion is painful, the refactor will be too.
the logging library surgery is real. but deletable-code thinking too often becomes cover for shipping throwaway stuff with no removal plan. choosing the deletion boundary before you build - that gap is what nobody actually does.
Oof, yes. "No removal plan" is the key. Deletability isn't permission to build slop — it's the discipline to build with exits.
Choosing the deletion boundary before you build is exactly the hard part. Most people read the essay, nod, and keep building like nothing changed. The boundary is where the thinking happens.
the boundary point is good. I'd also add - if removing it later breaks unexpected things, the boundary was wrong from the start. the exit plan ends up revealing design flaws you didn't know were there.
Sharp. The exit plan as a design audit - I love that.
If you can't delete it cleanly, you didn't build it cleanly. The removal doesn't create the mess, it just shows you where the mess already was.
that inverse reads like a theorem. most teams measure build quality six ways and never run the deletion audit — the coupling you can't undo cleanly is the review that didn't happen.
What a neat perspective! I had glimpses of similar thoughts, and you just put everything in a very clear way. A proper isolation - not just for the sake of isolation, but for the matter of keeping code clean and replaceable - is what we should strive for. Some parts can live for years, but most things are prone to change. Now with AI-delegated coding, things are changing even faster, so this point becomes stronger day after day.
Glad it landed. And you're right — AI changes the math. When code generation is cheap, deletion cost becomes the bottleneck, not writing cost.
Isolation isn't about elegance anymore. It's about keeping the door open.
This really resonates with me, especially the point about writing code that doesn't know too much. This has been a frequent reason for incidents and tricky debugging rounds in the past in teams I worked in.
Ignorant code is peaceful code. The moment a module knows too much about the rest of the system, it becomes a liability — not just for deletion, but for debugging, testing, and sleeping through on-call rotations.
Love this idea, especially for products where the UI keeps getting re‑imagined and the front/back split is constantly renegotiated. It’s a good reminder to treat APIs, view models, and even “frontend vs backend” as disposable boundaries instead of permanent truths.
such a good extension. "Disposable boundaries instead of permanent truths" - yes.
The frontend/backend split gets treated like a law of physics when it's really just a deployment decision. Being willing to redraw that line changes everything about how you design.
I mostly agree but with some caveats. think there could be well designed pieces of code integral to your code base that is basically impossible to rip out. If it's core to the product it doesn't really apply
Fair. But "core to the product" and "impossible to rip out" aren't the same thing. The database is core. You still want it behind an interface so you could replace it if you had to.
The caveat I'd add: some things are worth the coupling. Just know you're choosing it.
I used to think hiding the database behind an interface was a good practice and I don't do it anymore.
I did put the database behind an interface in some systems I built. The downside is extra plumbing that stops you from using specialized features. The few times Ive actually swapped the entire database is because of having to use such special features or because of performance. In those cases the interface is in the way anyway.
True engineering genius isn't about building complex abstractions, but about writing modules isolated enough to be deleted without friction.
snaptube vidmate
Hard agree. Genius is boring when it's done right. Just modules. Just boundaries. Just the quiet confidence that nothing you wrote is irreplaceable.
Fantastic piece. Reframing durability as deletability is a powerful shift in mindset. I especially liked the practical checklist and the focus on clear boundaries, small modules, and seams (feature flags, adapter layers) that act as deletion handles; treating abstractions as things you can safely swap or remove makes refactors and pivots far less risky. I’ll start asking “what would it take to remove this?” in code reviews - that lens alone will save time and reduce technical debt.
Appreciate this. That review question is the whole thing in one sentence. Hope it saves you some late nights.
As a beginner, this post really gave me a useful way to think about code.
I jumped straight into learning how to code, so at first I treated programming like solving a puzzle , I kept adding pieces until the feature worked. Once it was done, I usually avoided touching it because I knew changing one thing could easily break something else.
This post gave me a better way to think about writing software.
I’m getting into system design now, and it’s helping me be more intentional about the code I write, not just making the pieces fit, but thinking about how those pieces will change, evolve, or be removed later.
This made my day. A beginner picking up "deletability" before they've had to learn it the hard way? That's rare.
Most of us only get here after years of being burned. You just skipped a bunch of scars.
The puzzle mindset is exactly right — and it's so tempting. But software isn't a finished jigsaw. It's a garden. Things grow, die, get pulled out. Designing for that from the start is a superpower. Stick with it.
This essay matters more in the AI coding era than it did when Tef wrote the line. Models generate working code in seconds. The bottleneck isn't writing it anymore it's deleting it when reality disagrees with the assumption.
Right. AI makes creation almost free. That flips the whole economy. Value shifts from "can you build it" to "can you unbuild it safely when it's wrong."
Which means deletability isn't a nice-to-have anymore. It's the core skill.
This really clicked for me.
We had the same issue with theming in a multi-client app. Instead of spreading colors across 1000+ components, we centralized everything into one theme layer (CSS variables) and switch it based on the client/host.
Now when a client changes their palette, it’s a one-file update instead of a massive refactor.
Exactly your point: the abstraction isn’t for cleverness — it’s there so change (or even removal) is simple.
Perfect example. The theme layer is the seam. 1000+ components touching colors directly would be a nightmare — not just to change, but to even consider changing.
That's the quiet win: boundaries that make you brave enough to say "yes" to a request instead of dreading it.
What really stood out to me here is how often “extensible” code slowly turns into tightly coupled code over time. I’ve seen features that started as small utilities eventually become impossible to remove because they quietly leaked into configs, shared helpers, background jobs, analytics hooks, and half the API surface. By the time you try replacing them, the dependency graph is so spread out that even a small change feels risky.
The part about abstraction creating isolation instead of just reducing repetition is probably the most important point in your whole piece. A lot of teams apply 'DRY' too aggressively and accidentally create shared logic that eventually becomes organizational infrastructure. Sometimes duplication is actually cheaper than coupling, especially when it preserves module independence and keeps deletion cost low.
Also liked the framing of code review through the lens of “what would it take to remove this later?” Thats such a practical architectural signal. A feature that works but touches 20 unrelated files is usually telling you something important about boundaries, ownership, or hidden dependencies long before it becomes a real maintenance problem
You just articulated something I felt but didn't fully write:
That's the betrayal. It doesn't start coupled. It starts innocent. A config flag here, a shared helper there, an analytics hook someone added six months later. Death by a thousand small extensions.
And you're right about DRY as the culprit. We've been taught that repetition is the enemy. But coupling is worse. I'll take duplication over entanglement almost every time.
The code review lens catches this exactly. A feature that touches 20 unrelated files isn't necessarily wrong - but it's announcing something. Usually that something is "I don't respect your module boundaries, good luck deleting me."
Spent three months building a "future-proof" data layer with generic traits, plugin architecture, configurable everything.
Six months later, the data source changed format. Had to delete the whole thing and rewrite it.
The abstractions made deletion harder, not easier. Plugin architecture meant understanding the plugin interface just to delete a plugin. Generic traits meant tracing type parameters across 12 files.
Next time: simplest thing that works, isolated behind a clear interface, accept that it might all get deleted in six months.
What actually predicts whether something will be a pain to remove later? Counting files touched doesn't tell the whole story.
This is the wisdom. You built what the essay warned against — abstractions for permanence that became obstacles to change.
Files touched is a proxy, not the truth. The real predictor is entanglement:
A 3-file abstraction that's deeply entangled is harder to delete than a 15-file module that's a clean leaf.
The plugin architecture that requires understanding the interface just to remove a plugin - that's the smell. Deletion shouldn't demand comprehension. It should be mechanical: drop the file, remove the registration, done.
Your "simplest thing, clear boundary, accept deletion" is the real lesson. Three months of future-proofing bought you nothing but friction. The six-month rewrite was inevitable either way, one path just cost you more.
‘If it works, don’t touch it’
The unofficial strategy for avoiding deletability by fear-driven design. :)
Ha. "If it works, don't touch it" - also known as "the code is too scary to delete so we'll just build around it forever."
That's not durability. That's debt with a smile.
FUDDD • Fear Uncertainty Doubt Driven Development
is the line that captures the whole post. Most bad abstractions I've seen weren't bad because they were wrong — they were bad because they wrapped internal logic that had no reason to vary, while leaving external dependencies (logging, HTTP clients, DBs) bare and tangled through the codebase. We DRY the wrong things.
Exactly. We DRY the formatting helper that appears twice, but we let the database client touch 80 files.
That's the inversion. The things likely to change or need replacement — those deserve the abstraction. The little utility functions? Duplicate them and move on.
"Abstract at the seams, not in the middle" keeps coming back as the line people quote. Glad it landed.
The logging example hit differently for me I'm currently contributing to a Ruby on Rails codebase for GSoC and I've been navigating exactly this tension. When I first opened the repo, I noticed patterns that had clearly started as clean abstractions and slowly become the thing you described: load-bearing infrastructure that everyone is too nervous to touch.
What I keep coming back to is your point about abstracting at seams rather than in the middle. I think a lot of us reach for abstraction the moment we see repetition, but the question "what would it take to remove this?" reframes the why behind the abstraction entirely. You're not hiding complexity you're creating a handle.
I also liked @monom pushback in the comments about core code being "basically impossible to rip out." I think it's a fair caveat, but it feels like the exception rather than the rule. In practice, most code I've seen treated as permanent that nobody touches isn't permanent because it's right for the job. It's permanent because the deletion cost was never thought about at the design stage, so now it's too scary to approach. That's a different thing entirely.
The checklist at the end is genuinely useful. "Could I delete this feature with a single PR?" is the kind of question that makes you immediately reconsider two or three decisions you made in the last hour.
Really thoughtful — and impressive you're thinking this way during GSoC. Most contributors at that stage are just trying to get stuff working.
You nailed the distinction: "permanent because it's right" vs "permanent because it's too scary to delete." Those feel identical from the outside but are worlds apart inside.
And yeah, that review question has a way of making you delete your own code before anyone else has to. That's the best time to catch it.
Make the comment feel like it adds signal to the discussion, not noise.
What lands for me is the redefinition of abstraction — not as a tool for eliminating repetition, but as a tool for creating isolation so something can be removed cleanly later. That flips a lot of the conversations I've had about "over-engineering" on their head.
Usually the critique of abstraction is that it's premature — you're solving problems that don't exist yet. But if you think of an abstraction as a deletion seam rather than a scalability bet, it starts to feel less like prediction and more like insurance. You're not guessing what the system will need. You're making a bet that this particular boundary is one you might want to change independently someday, and the abstraction is just you respecting that uncertainty.
I think what makes this hard to teach is that it requires a kind of intuition about where change is likely to happen. Some boundaries are obvious — I/O, third-party APIs, storage. But a lot of the expensive deletions I've seen came from boundaries that seemed stable at the time and turned out not to be. I'm not sure there's a shortcut for developing that instinct other than living through a few painful extractions and swearing "never again."
This is the sentence that flips the whole conversation. Over-engineering critiques usually assume you're predicting growth. But if you're predicting change — even contraction - suddenly that "extra" abstraction looks like cheap insurance, not grandiosity.
And yeah, the instinct is hard to teach. Pain is still the best teacher. But framing it this way at least gives people a map before they get hurt.
awesome points!!! Great article!!!
thank you!!!
checkout: prachub.com
Great piece. Been adopting this mentality and it helps a lot
Isolation is about replaceability/removability not just for passing the DRY test
DI is really useful here
Totally. DI is deletability infrastructure - it makes the dependency someone else's problem at the boundary.
And ya, isolation that only serves DRY is just entanglement with extra steps. Isolation that serves replaceability is the real thing.
a good mental model to keep at the back of your head when writing code, especially when working on features that integrate with other services
yes sirr
this hits different. been working on a side project and keep over-engineering it. gonna try the "easy to delete" mindset
Side projects are where this mindset saves the most. You're not getting paid to maintain the over-engineering later. Just you, at 2am, wondering why you added a factory for a thing that appears once.
Ship the dumb version. Delete it guilt-free when you change your mind.