[{"data":1,"prerenderedAt":368},["ShallowReactive",2],{"navigation":3,"/blog/copy-paste-code-vs-reuse":141,"/blog/copy-paste-code-vs-reuse-surround":361},[4,36,55,80,101,116,128],{"title":5,"path":6,"stem":7,"children":8,"icon":35},"ASP.NET Core","/aspnet-core","1.aspnet-core/1.index",[9,11,15,19,23,27,31],{"title":10,"path":6,"stem":7},"Cursor Pagination",{"title":12,"path":13,"stem":14},"Dapper - Micro ORM","/aspnet-core/dapper","1.aspnet-core/2.dapper",{"title":16,"path":17,"stem":18},"DbUp - Database Migrations","/aspnet-core/dbup","1.aspnet-core/3.dbup",{"title":20,"path":21,"stem":22},"Serilog - Structured Logging for .NET","/aspnet-core/serilog","1.aspnet-core/4.serilog",{"title":24,"path":25,"stem":26},"Class Inheritance in C#","/aspnet-core/inheritance","1.aspnet-core/5.inheritance",{"title":28,"path":29,"stem":30},"C# Collections","/aspnet-core/collections","1.aspnet-core/6.collections",{"title":32,"path":33,"stem":34},".NET gitignore Command","/aspnet-core/gitignore","1.aspnet-core/7.gitignore",false,{"title":37,"path":38,"stem":39,"children":40,"icon":35},"Oauth2.0 and OpenIdConnect","/oauth2andopenidconnect","10.oauth2andopenidconnect/1.index",[41,43,47,51],{"title":42,"path":38,"stem":39},"RFC 6749 - OAuth 2.0 Authorization Framework",{"title":44,"path":45,"stem":46},"Authentication (AuthN) vs Authorization (AuthZ)","/oauth2andopenidconnect/authnandauthz","10.oauth2andopenidconnect/2.authnandauthz",{"title":48,"path":49,"stem":50},"TOTP Authentication","/oauth2andopenidconnect/totp","10.oauth2andopenidconnect/3.totp",{"title":52,"path":53,"stem":54},"OAuth 2.0 Resource Indicators - RFC 8707","/oauth2andopenidconnect/oauth2resourceindicators","10.oauth2andopenidconnect/4.OAuth2ResourceIndicators",{"title":56,"path":57,"stem":58,"children":59,"page":35},"Azure","/azure","2.azure",[60,64,68,72,76],{"title":61,"path":62,"stem":63},"Azure Cost Management","/azure/azuremanagementandgovernance","2.azure/1.Azuremanagementandgovernance",{"title":65,"path":66,"stem":67},"Azure Policy","/azure/azurepolicy","2.azure/2.azurepolicy",{"title":69,"path":70,"stem":71},"Code Blocks","/azure/code-blocks","2.azure/3.code-blocks",{"title":73,"path":74,"stem":75},"Prose Components","/azure/prose-components","2.azure/4.prose-components",{"title":77,"path":78,"stem":79},"Images and Embeds","/azure/images-embeds","2.azure/5.images-embeds",{"title":81,"path":82,"stem":83,"children":84,"page":35},"Git","/git","3.git",[85,89,93,97],{"title":86,"path":87,"stem":88},"Git Rebase","/git/git-rebase","3.git/1.git-rebase",{"title":90,"path":91,"stem":92},"Git Stash","/git/git-stash","3.git/2.git-stash",{"title":94,"path":95,"stem":96},"SemVer","/git/semver","3.git/3.semver",{"title":98,"path":99,"stem":100},"Conventional Commits","/git/conventional-commits","3.git/4.conventional-commits",{"title":102,"path":103,"stem":104,"children":105,"icon":35},"Design Patterns","/design-patterns","7.design-patterns/1.index",[106,108,112],{"title":107,"path":103,"stem":104},"Introduction",{"title":109,"path":110,"stem":111},"Installation","/design-patterns/installation","7.design-patterns/2.installation",{"title":113,"path":114,"stem":115},"Usage","/design-patterns/usage","7.design-patterns/3.usage",{"title":117,"path":118,"stem":119,"children":120,"icon":35},"Software Principles","/principles","8.principles/1.index",[121,122,125],{"title":107,"path":118,"stem":119},{"title":109,"path":123,"stem":124},"/principles/installation","8.principles/2.installation",{"title":113,"path":126,"stem":127},"/principles/usage","8.principles/3.usage",{"title":129,"path":130,"stem":131,"children":132,"icon":35},"Software Architecture","/architecture","9.architecture/1.index",[133,135,138],{"title":134,"path":130,"stem":131},"CQRS Pattern",{"title":109,"path":136,"stem":137},"/architecture/installation","9.architecture/2.installation",{"title":113,"path":139,"stem":140},"/architecture/usage","9.architecture/3.usage",{"id":142,"title":143,"body":144,"date":351,"description":352,"extension":353,"featured":35,"meta":354,"navigation":355,"path":356,"readTime":357,"seo":358,"stem":359,"tag":117,"__hash__":360},"blog/blog/1.copy-paste-code-vs-reuse.md","There's No Glory or Gain in Multiple Duplicated Implementations",{"type":145,"value":146,"toc":339},"minimark",[147,151,154,159,162,165,169,172,184,188,191,232,236,245,256,260,263,267,270,273,276,279,283],[148,149,150],"p",{},"Every developer has done it: finish a feature, remember writing something similar six months ago in another repo, copy the file over, rename a few things, and move on. It feels fast. It feels productive. And it is — for about a week.",[148,152,153],{},"The trouble with copy-pasting code isn't that it's lazy. It's that it quietly converts a one-time savings into a recurring, compounding cost that somebody has to keep paying, usually without realizing they signed up for it.",[155,156,158],"h2",{"id":157},"copying-isnt-reuse-its-duplication-wearing-reuses-clothes","Copying Isn't Reuse — It's Duplication Wearing Reuse's Clothes",[148,160,161],{},"It's tempting to think of copy-pasted code as \"reusing\" what you already built. It isn't. Genuine reuse means there's exactly one implementation, consumed by many applications. Copying produces the opposite: many independent implementations that merely started from the same source and immediately began drifting apart the moment any one of them got touched.",[148,163,164],{},"That drift is the whole problem in miniature. Six projects copy the same email helper. A year later, one has a bug fix the others don't, another added logging nobody backported, a third quietly changed a timeout value with no comment explaining why. They still share a name. They no longer share behavior. Nobody who joins the team later has any way to know that \"EmailHelper.cs\" in Project C isn't the same code as \"EmailHelper.cs\" in Project A — until something breaks in a way that only makes sense once you diff the two files line by line.",[155,166,168],{"id":167},"every-bug-you-ship-gets-shipped-again","Every Bug You Ship Gets Shipped Again",[148,170,171],{},"The cost shows up most sharply the day someone finds a real problem — a security issue, a data-handling bug, a race condition. If the logic lives in one place, the fix is: find it, fix it, deploy it, done. If it's been copied into a dozen repositories, the fix is: find every copy, hope you actually found all of them, fix each one individually, test each one individually, and deploy each one on its own schedule. The repository nobody remembered about becomes next quarter's incident report.",[148,173,174,175,179,180,183],{},"This is precisely the failure mode the DRY principle — \"Don't Repeat Yourself\" — was coined to prevent. Programmers Andy Hunt and Dave Thomas introduced it in ",[176,177,178],"em",{},"The Pragmatic Programmer",", defining it as the idea that every piece of knowledge in a system should have a single, unambiguous, authoritative representation. It's worth being precise about what that actually means, because it's widely misquoted as \"never copy code,\" which isn't quite it. DRY isn't really about the code — it's about not letting the same piece of ",[176,181,182],{},"knowledge"," (a business rule, a validation constraint, a timeout policy) live in more than one place, because every additional copy is another place that can silently fall out of sync with the truth.",[155,185,187],{"id":186},"duplication-discourages-the-very-improvement-it-should-invite","Duplication Discourages the Very Improvement It Should Invite",[148,189,190],{},"Here's the part that's easy to miss: once code exists in twenty repositories, people stop improving it — not out of negligence, but because improving one copy while nineteen others stay behind doesn't actually fix anything. It just adds a twentieth variant to keep track of. The rational response to that situation is to leave it alone, and that's exactly what happens. Code that should be getting better over time instead calcifies, because there's no longer a single place where an improvement pays off for everyone at once.",[148,192,193,194,201,202,207,208,213,214,219,220,225,226,231],{},"Shared libraries flip that dynamic. When a validation helper, an HTTP client, or an authentication component lives in one internal package consumed by many applications, a single bug fix, a single performance improvement, a single security patch reaches every consumer at once. The ecosystem gets better together instead of one silo at a time. Modern package registries — ",[195,196,200],"a",{"href":197,"rel":198},"https://www.nuget.org/",[199],"nofollow","NuGet"," for .NET, ",[195,203,206],{"href":204,"rel":205},"https://www.npmjs.com/",[199],"npm"," for JavaScript, ",[195,209,212],{"href":210,"rel":211},"https://maven.apache.org/",[199],"Maven"," for Java, ",[195,215,218],{"href":216,"rel":217},"https://pypi.org/",[199],"PyPI"," for Python, ",[195,221,224],{"href":222,"rel":223},"https://crates.io/",[199],"Cargo"," for Rust — exist specifically to make this easy, and most support private feeds so an organization can publish and consume internal packages the same way it consumes open-source ones. Some companies formalize this practice further under the banner of ",[195,227,230],{"href":228,"rel":229},"https://en.wikipedia.org/wiki/Inner_source",[199],"\"InnerSource\""," — applying open-source collaboration norms to code shared across internal teams.",[155,233,235],{"id":234},"dry-has-a-real-counterargument-and-it-matters","DRY Has a Real Counterargument — and It Matters",[148,237,238,239,244],{},"None of this means every ten-line helper deserves its own package, and it's worth taking the opposing view seriously rather than treating DRY as an absolute law. In a widely cited 2016 post, ",[195,240,243],{"href":241,"rel":242},"https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction",[199],"Sandi Metz"," argued that duplication is far cheaper than the wrong abstraction. Her point: when two pieces of code look similar but aren't actually the same underlying concept, forcing them into one shared implementation — usually by bolting on parameters and conditionals to handle each caller's quirks — produces something worse than duplication ever was: a single piece of code that nobody fully understands, serving multiple unrelated purposes at once, that everyone is now afraid to touch.",[148,246,247,248,251,252,255],{},"The two ideas aren't actually in conflict once you separate what each is warning against. DRY is a caution against letting the ",[176,249,250],{},"same knowledge"," live in multiple unsynchronized places. Metz's warning is against forcing ",[176,253,254],{},"coincidentally similar"," code — which represents different knowledge that only happens to look alike right now — into one shared abstraction before you're sure it's really the same thing. Good candidates for real shared libraries are the ones that pass both tests: they represent one piece of knowledge (how we authenticate a user, how we validate an email address, how we sign a request) that genuinely should stay in sync everywhere it's used, not just code that happens to look similar today.",[155,257,259],{"id":258},"when-copying-is-actually-fine","When Copying Is Actually Fine",[148,261,262],{},"There are legitimate cases for copying, and it's worth naming them so the rule doesn't get cargo-culted into \"never copy anything, ever.\" Prototypes, throwaway scripts, one-off migrations, and tutorial code are all reasonable places to duplicate freely — the code isn't going to live long enough for drift to matter, and building a shared package for something used exactly once is pure overhead. The mistake isn't copying in these situations. It's letting temporary duplication quietly become permanent architecture because nobody circled back to it once the \"temporary\" script turned into a load-bearing part of the system.",[155,264,266],{"id":265},"the-real-payoff-is-leverage-not-lines-of-code","The Real Payoff Is Leverage, Not Lines of Code",[148,268,269],{},"The deepest cost of copy-paste programming isn't the duplicated code itself — it's the time it steals from everything else. You might feel like you're doing interesting work each time you extend or tweak a copied helper, but the returns on that time are minimal, because you're not actually building anything new — you're carrying the same bugs, the same edge cases, and the same design mistakes from one place to the next, patching around them locally instead of fixing them once. Every copied utility becomes one more thing that has to be independently maintained, debugged, tested, documented, upgraded, and secured, multiplied by every project it was pasted into. Fixing the same bug in ten repositories is ten repositories' worth of time that could have gone into a new feature, a performance improvement, or an actual hard problem worth solving.",[148,271,272],{},"Great engineers tend not to measure their output in lines written. They measure it in future work eliminated. A well-designed shared library, built once and consumed everywhere it's needed, can save an organization hundreds of hours across teams that never have to think about it again. That's leverage — and it's the opposite of what copy-pasting produces, which is a debt that grows every time someone reaches for Ctrl+C instead of asking whether this is the second or third time they've written the same thing.",[148,274,275],{},"There's a version of this that's worth calling out directly, because it's where a lot of copy-paste programming actually starts: doing the same kind of integration or solving the same shape of problem for the third or fourth different project or client. That repetition isn't just a chance to reuse code faster — it's a chance to notice what went wrong last time and fix it going forward. The team that copies the same payment integration into a fifth codebase ships the same edge-case bugs, the same awkward error handling, and the same workarounds a fifth time. The team that pauses at that point — the moment a pattern has shown up enough times to actually be a pattern, not just a coincidence — and asks \"what did we learn building the last four of these?\" ends up with something better than any individual copy ever was: a shared implementation that's already had its rough edges found and fixed, instead of five separate implementations each carrying a different subset of the same mistakes. This is exactly the moment Sandi Metz's advice and the DRY principle stop pulling in different directions and start agreeing with each other — by the third or fourth similar integration, you're no longer guessing whether the pattern is real; you actually know, and that's when consolidating it pays off instead of backfiring.",[277,278],"hr",{},[155,280,282],{"id":281},"references-and-further-reading","References and Further Reading",[284,285,286,300,308,316],"ul",{},[287,288,289,290,293,294,299],"li",{},"Andy Hunt and Dave Thomas, ",[176,291,292],{},"The Pragmatic Programmer: From Journeyman to Master"," (1999) — the book that coined the DRY principle. See also Wikipedia's overview: ",[195,295,298],{"href":296,"rel":297},"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself",[199],"\"Don't repeat yourself\"",".",[287,301,302,303,307],{},"Sandi Metz, ",[195,304,306],{"href":241,"rel":305},[199],"\"The Wrong Abstraction\""," (2016) — the essay behind \"duplication is far cheaper than the wrong abstraction.\"",[287,309,310,311,315],{},"Wikipedia, ",[195,312,314],{"href":228,"rel":313},[199],"\"Inner source\""," — applying open-source collaboration practices to internal code sharing.",[287,317,318,319,322,323,326,327,330,331,334,335,338],{},"Package registries referenced above: ",[195,320,200],{"href":197,"rel":321},[199]," (.NET), ",[195,324,206],{"href":204,"rel":325},[199]," (JavaScript), ",[195,328,212],{"href":210,"rel":329},[199]," (Java), ",[195,332,218],{"href":216,"rel":333},[199]," (Python), ",[195,336,224],{"href":222,"rel":337},[199]," (Rust).",{"title":340,"searchDepth":341,"depth":342,"links":343},"",1,2,[344,345,346,347,348,349,350],{"id":157,"depth":342,"text":158},{"id":167,"depth":342,"text":168},{"id":186,"depth":342,"text":187},{"id":234,"depth":342,"text":235},{"id":258,"depth":342,"text":259},{"id":265,"depth":342,"text":266},{"id":281,"depth":342,"text":282},"2026-07-10","Copy-pasting code quietly converts a one-time saving into a compounding cost. On DRY, Sandi Metz's \"Wrong Abstraction,\" and when duplication is actually fine.","md",{},true,"/blog/copy-paste-code-vs-reuse","8 min",{"title":143,"description":352},"blog/1.copy-paste-code-vs-reuse","onk92KrXlOVV_BaP6JHfyo2VR4Ck_SK0I-kr1XjEXns",[362,363],null,{"title":364,"path":365,"stem":366,"description":367,"children":-1},"Hard Skills Get You in the Room. Ownership Is What Keeps You There.","/blog/ownership-and-career-growth","blog/10.ownership-and-career-growth","Technical depth gets an engineer into the room. What determines whether they thrive over years is how well they collaborate, absorb feedback, and own outcomes nobody assigned them.",1785744836817]