The Fastest Way to Start an App Is to Copy One
A family of small applications had a parent: a travel app with a shape worth reusing — local-first capture, a queue that survives being offline, an assistant panel bound to a real conversation, a publish path to a shared stream. Two new siblings were wanted the same week: a journal built on an open timeline, and a health log that observes and never diagnoses. Both were seeded from the parent's shape, by two different hands, on the same afternoon.
This is not a story about carelessness. Seeding from a working sibling is the correct move, and it is what the family still does today. The interesting part is what the copies did over the next few hours, entirely without anybody deciding anything.
The Inventory, Taken Hours Later
By evening the three repositories held this, and it is worth reading as a list rather than as a summary, because the shapes are all different:
- A byte-identical client layer for talking to the assistant — streaming, conversation binding, turn projection — three times over. Pure copy, no drift yet.
- The same image-fitting step ported twice, differently: one implementation reached for a Python imaging library, the other shelled out to a system tool. Same intent, two behaviors, and nobody had compared them.
- Three implementations of the same publish sequence, each writing a different record schema for what is conceptually one event: this crumb was published, here is where it landed.
- Three settings stores for the same device-local preferences, already using three different field names for the same field.
Four distinct failure shapes, and only the first one is what people usually picture when they say "duplicated code". The second is a fork wearing a copy's clothes. The third is three incompatible histories of the same act. The fourth is a vocabulary split, which is the one that will bite in the next lesson.
Why an Inventory and Not an Argument
Everyone involved already agreed that duplication is bad. That agreement produced exactly zero shared code, because it is abstract, and every individual copy is concrete. The thing that actually moved the decision was a list: four named layers, with what each one had already done, written out where two people could look at the same text.
This is a general property of the problem. "We have too much duplication" is unactionable — it names no boundary, so it cannot be scoped, staffed, or finished. "These four layers are triplicated, and here is what each copy did differently" is a work order. The first sentence has been true in most codebases for years. The second one has an end.