Put the Catalogue in Data and Growth Becomes a Row Instead of a Release
Every product has one thing certain to grow: the list of cities, currencies, carriers, document types or tenants it supports. Where that list lives is decided on day one and cannot be revisited cheaply, because by the time it matters the launch set is wired through the application. Modelled as data, adding to it is a row. Modelled as code, it is a ticket, a deployment and a regression pass, forever.
Put the Catalogue in Data and Growth Becomes a Row Instead of a Release
The short answer. Nearly every product has a catalogue: the set of cities, currencies, carriers, jurisdictions, document types or tenants it handles. Whether that catalogue is data or code is decided implicitly in the first week, and it is one of the few architectural choices that cannot be revisited cheaply, because by the time the second entry is needed the first one is woven through the application. As code, every addition is an engineering ticket, a deployment and a regression pass, which puts a permanent tax on the one axis the business most wants to move. As data, it is a row. One product shipped 88 airports at launch and expanded its corridor set with zero rewrites, because the decision was made before the first corridor rather than after it.
The pressure at the start of a build is entirely toward hard-coding. There is one launch market, one currency, one document type. Generalising for a second case that does not exist yet is speculative, and speculative generality is a well-known way to waste a quarter.
So the launch case goes in directly, which is defensible, and the cost arrives later in a form nobody attributes to that decision.
What hard-coding actually costs
Not the first addition. The first one is easy, because the code is fresh and somebody remembers how it works.
The cost is structural and it compounds.
Every addition is an engineering transaction. A ticket, a developer, a review, a deployment, a regression pass. The business wants to add a market and the answer is a sprint. That is a tax on growth, levied on the axis the business cares most about.
The knowledge sits in the wrong place. The person who knows what the new market requires is in operations or commercial. The person who can change it is an engineer. Every addition is a translation between them, and translations lose things.
The launch case becomes an assumption. Hard-coded values do not stay in one file. A currency assumption reaches formatting, rounding, validation and reporting. The second currency is not one change, it is an archaeology project across every place the first one leaked.
Testing degrades. With the catalogue in code, tests are written against the launch case, so they pass for reasons that will not hold for the second entry.
What "as data" requires
Putting the catalogue in data is not just a table. The entries have to carry everything that varies, which is where it actually gets designed.
For each entry: its identifier, its display name, and then every parameter that differs. Thresholds, rules, formats, which optional behaviours apply. If any of those are still in code, branching on the identifier, the catalogue is not in data. It is in data with a code appendix, and the appendix is where the next change goes.
The useful test is whether adding an entry requires touching code. If it requires a code change for any entry of the same kind, the modelling is incomplete.
Two more things earn their place immediately.
Effective dates. Entries change. A rule is corrected, a threshold moves, a market's requirements shift. Without dates, you cannot reproduce what the system did last month, and reproducing past behaviour is exactly what you need when somebody disputes an old result.
Provenance. Where did this entry come from and when was it last confirmed. A catalogue that cannot say how old an entry is degrades silently, because a stale row still returns an answer.
The boundary it does not cross
Data-driven expansion covers entries of the same kind. This is the limit worth stating clearly, because over-promising here is how the approach gets discredited.
A new airport is the same kind of thing as the existing airports: it has a location, buffers, rules of types the engine already understands. A row.
A genuinely new itinerary structure, say multi-hub chains rather than a single intermediate point, is not a new entry. It is a new shape the engine does not model, and it is an engine change. Calling that a configuration row would be wrong, and pretending the catalogue covers it is how a team ends up with a configuration language that is secretly a programming language nobody can debug.
The honest statement is: entries of the kinds we model are rows, new kinds are engineering. Then the question for any request becomes which of the two it is, which is a quick and useful conversation rather than an estimate.
Prove the engine narrow first
The complementary move, and the one that keeps this from becoming speculative generality.
Model the catalogue as data from the start, then validate the engine against a single entry. One corridor, one market, one document type, where the behaviour can be checked against reality in detail.
This is the opposite of building for many and testing with one. The structure supports many and the validation is deep on one, which is the combination that holds. You find out whether the engine is right where you can actually tell, and expansion is configuration rather than code because the structure was there from the beginning.
The cost of this over hard-coding is small, measured in days at the start. The cost of the reverse is measured in the first expansion and every one after it.
Where this applies
Markets and jurisdictions, currencies and tax rules, carriers and service levels, document types and extraction rules, tenants and their configurations, languages and locales, product categories and their validation rules, integrations and their adapters.
The test for any of them: is there a list the business will want to extend, and is extending it currently an engineering task? If yes to both, that list belongs in data, and the longer it stays in code the more expensive moving it becomes.
The honest limit
A data-driven catalogue moves work from engineering to operations, which is only an improvement if operations can actually do it. A table nobody outside the team can safely edit is a hard-coded catalogue with extra steps and a worse failure mode, since a bad row deploys instantly with no review.
So the catalogue needs validation on write, a review path for changes that matter, and ideally a way to test an entry before it goes live. Otherwise you have replaced a slow, safe process with a fast, dangerous one, and the first bad row will be an incident rather than a failed build.
FAQ
When should I move a catalogue from code to configuration? Before the first entry ships, because afterwards the launch case has leaked into formatting, validation, rounding and reporting, and the second entry is an archaeology project rather than a change. The decision is cheap on day one and expensive to revisit.
Isn't building for a second case speculative generality? Modelling the catalogue as data is not the same as building features for cases you do not have. The structure costs days at the start and you still validate deeply against one entry. Speculative generality is building the second case. This is declining to weave the first one through the application.
How do I know whether my catalogue is genuinely in data? Ask whether adding an entry of the same kind requires any code change. If anything branches on the identifier, the catalogue is in data with a code appendix, and the next change will go in the appendix.
What does a catalogue entry need beyond an identifier and a name? Every parameter that varies between entries, plus effective dates and provenance. Without dates you cannot reproduce what the system did last month, which is exactly what you need when an old result is disputed. Without provenance the catalogue degrades silently, since a stale row still returns an answer.
What cannot be handled by adding a row? A new kind of thing rather than a new instance of a known kind. A new market is a row. A fundamentally different structure the engine does not model is an engine change. Pretending otherwise produces a configuration language that is secretly a programming language nobody can debug.
What is the risk of moving a catalogue into data? That a bad row now deploys instantly with no review. The catalogue needs validation on write, a review path for significant changes, and ideally a way to test an entry before it goes live, otherwise you have swapped a slow safe process for a fast dangerous one.
Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.
Bring us the program