---
title: "Economies and diseconomies of scale in code: what economists know about DRY"
description: "What economies of scale, and where they end, say about DRY: sharing code pays up to a point, and economics tells you where that point is."
published: "2026-10-11"
---

# Economies and diseconomies of scale in code: what economists know about DRY

Published **11 October 2026**

## TL;DR

- Shared code is a fixed cost spread over its uses: it pays only once there are enough of them, and the rule of three is a rough guess at that break-even point.
- Past a certain size, every user of a shared abstraction pays for the needs of all the others, and that coupling cost can outgrow the savings.
- Keeping a duplicate on purpose is an option, not a failure: it leaves you free to abstract later, once you know what the copies really share.

"Don't repeat yourself" is one of the first rules we learn as developers, and one of the most satisfying to apply: spot two similar blocks, pull them into a shared function, delete a dozen lines (very satisfying :D). Most of what I read about DRY celebrates those savings. Far less of it asks when sharing stops paying.

I have a personal reason to ask. For my side projects I keep a small private TypeScript library, `deno-tools`, with a rule that goes with it: when a project needs something the library could hold, extend the library, never copy its code. Building this blog meant four changes to that library in a single day before the blog would build, and it made me wonder whether my rule is a good deal. Economics turned out to have the words for it and, more usefully, a way to tell where the deal ends.

The whole mapping at a glance, with the details below:

| Economics | Programming | What it adds |
| --- | --- | --- |
| Economies of scale: a fixed cost spread over output | Writing a shared function once; each new use is cheap | The basic analogy |
| Minimum efficient scale | The rule of three: abstract only at the third copy | Abstraction has a break-even point, and you can estimate it |
| Diseconomies of scale (coordination, bureaucracy) | Shared code that has to serve every user, so each change ripples to all of them | Past a certain size, sharing costs more than it saves |
| Lock-in and switching costs | Being tied to an abstraction that is hard to change | The hidden long-term cost |
| Economies of scope: shared inputs across *different* products | One library used by unrelated projects | A closer fit for DRY than scale |
| Coase: make or buy, and the cost of each transaction | Write it, depend on a library, or copy it | Explains when *not* to share |
| Real options | Keeping a duplicate on purpose, to abstract later | A reason to wait that is easy to defend |

## Economies of scale: a fixed cost spread over its uses

A shared function is a fixed cost: you pay once to write, test and document it, and each new use is nearly free. My library's `fetch` is a good example: a drop-in replacement that retries failed requests, written once, and for any project that needs it the whole cost is one import.

```ts
import { fetch } from "./deno-tools/src/http/fetch.ts"

// Retries network errors and failed responses, five attempts by default
const res = await fetch("https://api.example.com/rates")
```

Spreading a fixed cost only works with enough output to spread it over. Written as a rule, sharing pays when the expected reuses × the saving per reuse is greater than the cost of building the abstraction plus the coupling cost it puts on each user. None of these can be measured precisely, but writing them down already changes the question from "is this duplicated?" to "will this be used enough to pay back?".

## Minimum efficient scale: the rule of three

Economists call the smallest output at which average cost stops falling the minimum efficient scale. Programming has a rough estimate of it: the rule of three, popularised by Martin Fowler's *Refactoring*: the first time, write it; the second time, wince and copy it; the third time, abstract. Two copies don't tell you what is really shared and what only looks alike. Payments have a neat example:

```ts
// Two copies look alike: amounts in cents, two decimals
const euros = (cents: number) => `${(cents / 100).toFixed(2)} EUR`
const francs = (cents: number) => `${(cents / 100).toFixed(2)} CHF`

// The third shows what varies: the yen has no minor unit
const format = (amount: number, currency: string, decimals: number) =>
  `${(amount / 10 ** decimals).toFixed(decimals)} ${currency}`
```

Abstract at two copies and you hard-code the two decimals, then undo it the day the yen arrives.

## Diseconomies of scale: when sharing gets too big

Firms run into diseconomies of scale: past some size, coordination and bureaucracy cost more than size saves. Shared code runs into the same thing: every user of a shared function pays for the needs of all the others, and changing it means checking every one of them. Sandi Metz put it in one line in [The Wrong Abstraction](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction): "duplication is far cheaper than the wrong abstraction".

My library had a small case of it. Its site builder worked out where each page goes by replacing `src` in the page's path with the output folder. That held as long as every site kept its sources in a folder named `src`, which they all did, until this blog needed a staging folder of its own:

```ts
// Before: correct only while the source folder is called "src"
const out = path.replace(/\bsrc\b/g, outputDir)

// After: the page's path under the source folder, whatever its name
const out = join(outputDir, relative(sourceDir, path))
```

The fix itself was one line. The cost was everything around it: tests, docs, a changelog entry, and, sooner or later, a library update in every project that builds a site, whether it needed the change or not. Lock-in is the long-term form of the same cost: the more projects lean on an abstraction, the harder it gets to change.

## Economies of scope: different projects, shared parts

Most of the value in my library isn't scale at all. Economies of scope are savings from sharing inputs across *different* products, which is closer to what it does: Telegram bots, an API, a command-line assistant and a few websites have little in common, but they share logging, that retrying `fetch` and a static site build. The Markdown renderer was written for this blog and now also builds my study notes. Ronald Coase asked why firms make some things and buy others; every dependency is the same choice between writing it, depending on it or copying it, and the answer depends on what integrating it costs.

## Real options: keeping the copy on purpose

A real option is the right, but not the duty, to act later. Keeping a duplicate on purpose buys one: you can still abstract once a third case shows what is shared, whereas an abstraction made too early has to be undone first, which costs more than the copy ever did.

```ts
// Copied from the bot on purpose: the two may still drift apart.
// Share it when a third project needs it.
const backoff = (attempt: number) => Math.min(1000 * 2 ** attempt, 30_000)
```

## The numbers so far

Since March 2025, eight of my projects have come to use the library, which holds about a dozen modules, and those projects have updated their copy of it 52 times. The costs are easy to count: those updates, and that day of fixes for the blog. The savings are code I never had to write, which is harder to count. My honest answer is that the rule pays for the parts every project uses, logging and builds, and I would not extend it to code only one project needs.

## Read next

- [A valid payment can still go to the wrong person: an exercise in fencing in an AI agent](a-valid-payment-can-still-go-to-the-wrong-person.md), 8 October 2026

## About the author

I'm Michele Durante, Team Lead Payments at RaiseNow. I write here about what I
learn in payments and fintech, and the engineering around them. Read more
[about me](about-me.md), see my [CV](cv.md), or follow new entries by
[RSS](feed.xml).
