Shared kernel vs copy the code: choose the cheaper path
Shared kernel vs copy the code looks like a small design choice, but it shapes cost, delays, and team friction. Learn when each option fits.

Why this choice gets expensive
Two domains can use the same business term and still mean different things. A team sees the same name in two places, puts the code in one shared package, and feels done. It looks tidy. The cost shows up later.
The problem starts when the rules stop moving together. Maybe both teams agree today on what an "approved account" means. Six months later, one team adds a waiting period while the other only cares whether billing is clear. The name stayed the same. The work around that name did not.
From that point on, one design choice slows both teams. A small edit needs discussion, extra testing, and more caution because nobody wants to break the other side. The code is shared, so the decision is shared too.
That cost is easy to miss because it rarely looks dramatic. It shows up as more coordination before simple changes, slower releases when one team has to wait, awkward rules added to satisfy two different needs, and a growing fear of touching code that "belongs" to everyone.
That is why shared kernel versus copied code is rarely a question of neatness. It is a question of change. If two domains will evolve at different speeds, shared code often becomes the expensive option even when it removes duplication today.
Copying the code can look messy on day one. It can still be cheaper because each domain gets room to adjust its own rules without asking permission. Shared code only stays cheap when meaning, timing, and ownership remain close for a long time. Shared names do not prove that. Future change does.
What a shared kernel really means
A shared kernel is a small piece of domain code that two domains own together. Both sides use the same names, the same rules, and the same tests because they mean the same thing in both places. It is not a convenience layer or a place to dump random shared code. It is a joint agreement.
That agreement matters more than the code itself. If one team thinks a concept means one thing and the other team means something slightly different, a shared kernel turns that mismatch into constant friction. When it works, both teams can point to one definition and trust it.
Joint ownership also changes how changes happen. No team can quietly edit the shared part and move on. Even a small update needs a decision from both sides, because both sides pay for the change. In practice, shared kernels work best when the concept is stable and the teams can talk often without a lot of process.
The tests matter just as much. If both teams share code but do not share tests, they do not really share a kernel. They share risk. Good shared tests make the rules visible and stop one side from breaking the other by accident.
Most teams get into trouble with size. A shared kernel should stay narrow, almost boring. Think a few core rules, a couple of value objects, maybe one validator. Once it pulls in helpers, workflow logic, or team specific exceptions, it stops being a clean agreement and starts becoming hidden coupling.
A simple rule helps: if two teams cannot explain the shared part in a few sentences, it is already too big.
What copying the code really means
Copying the code does not mean the team gave up on design. It means each domain owns its own version of the concept and can change it locally. The names may start the same, but the behavior often does not stay the same for long.
A copied model begins as a clone. After that, each team edits rules, fields, defaults, and edge cases to fit its own work. The code drifts over time because the business drifts too. Two departments can use the same word and still mean slightly different things.
That is why duplication is not always waste. Small duplication can cost less than constant negotiation. If one team changes a concept every week and another touches it once a quarter, forcing both teams to agree on every update can waste more time than a local copy ever will. Meetings, reviews, and compromise are real costs.
You still pay for this choice. A bug fix may need to land twice. Tests may split. A rule may change in one domain and stay frozen in another. That drift is not automatically bad. It only becomes a problem when teams expect sameness after choosing independence.
Copied code works best when the concept is related but not identical in business terms. In many shared kernel versus copied code decisions, the cheaper option is the one that gives each team room to move at its own pace. If the business needs differ, local ownership usually beats shared rules that nobody can change without a debate.
Start with speed of change
When two domains use the same concept, check how fast each side changes it. That tells you more than a neat diagram ever will. In many cases, change rate is the real cost driver.
Start with plain facts from the last few months. Count edits, not opinions. If one team touched the concept 12 times and the other touched it twice, they do not live at the same pace.
A quick check gives enough signal. How often does each team change the rules, fields, or names? Who asks for urgent fixes when the concept breaks? Does one side ship daily while the other ships once a month? When changes happen, do both sides need them or only one?
Release timing matters more than teams expect. If product code changes every week but finance code changes once per quarter, shared code can turn a small update into a coordination job. Now both sides need to review the change, fit it into their release cycle, and deal with version drift.
Copied code often wins when one side changes far more often. You pay a small upfront cost, then each team moves on its own schedule. That is often cheaper than repeated meetings, blocked releases, and surprise breakage.
Take a simple case like "customer status." A growth team may add statuses for trials, upsells, and win back campaigns every few weeks. An accounting team may only care about "billable" and "not billable." If they share one model, the slower side inherits churn it does not need.
If both sides change together, answer to the same owner, and release at about the same speed, shared code can make sense. If their clocks differ, copying usually costs less.
Compare the real costs
Most teams price this choice by counting coding hours. That misses the part that usually hurts more: coordination.
A shared kernel can save duplicate fixes and keep rules consistent. It also adds meetings, reviews between teams, version planning, and release timing problems. If Team A changes a rule on Tuesday and Team B cannot ship until Friday, that delay is part of the cost.
Copied code flips the bill. Teams move faster day to day because they do not need approval from each other. They pay later when the same bug appears twice, or when both teams patch the same rule in slightly different ways.
When you compare the options, count the work people often ignore: time spent discussing shared model changes, review time from the other team before a release, days lost while one team waits, bug fixes applied in two places, and cleanup after rules drift apart.
Watch who blocks whom when the business changes a rule. If one domain owns the concept and changes it often, a shared kernel can turn every update into a negotiation. That cost grows fast in startups, where product rules move every week.
If the rule changes slowly and both teams need the same meaning, sharing can still be cheaper. One fix, one source of truth, fewer mismatches. But if each team uses the concept in its own way, copied code may cost less overall because it avoids constant coordination.
Cheap code can create expensive coordination. A few hundred copied lines might cost less than months of review debt, release waits, and low grade team friction. Count calendar time and attention, not just lines of code.
Use a simple decision path
A short decision path cuts a lot of debate because it forces the team to name the concept and check where the rules already split.
Start on one page and keep it plain. If you need a paragraph full of caveats, the concept is probably too broad.
- Write the concept in one simple sentence. For example: "Customer status shows whether an account can buy, get support, or access premium features."
- List the rules both domains share right now. Use facts, not guesses.
- Mark the rules that already differ. That matters more than shared field names.
- Estimate how often the rules will change together.
- Choose shared code only when the common rules look stable for a while. If the rules will drift soon, copy the code and let each side move at its own pace.
A small example makes this concrete. Imagine the product team and the billing team both use "customer status." At first, they share three rules. Two months later, billing adds "past due" while the product team adds "grace period." If you forced a shared kernel too early, every change now needs coordination, reviews, and release timing across both teams.
That is why this decision is usually less about elegance and more about change patterns. Stable shared rules can live in one place. Rules that split under pressure should split in code too.
Write the shared rules down, circle the differences, and count the expected joint changes for the next six months. That usually makes the cheaper option obvious.
A simple example with customer status
When two teams say "customer status," they often mean different things. Sales may use status to decide who can buy, renew, or needs a billing follow up. Support may use status to decide how to handle a ticket right now.
Sales might keep three states: trial, active, and past due. Support might keep VIP, blocked, and waiting on finance. The label sounds shared, but the job is different.
This is where teams get trapped. They create one shared status model because it feels tidy. A month later, that model starts growing extra fields and flags so both teams can squeeze their needs into it. Sales wants billing states and renewal rules. Support wants service rules and queue priority. The shared model picks up exceptions for both.
Soon nobody can change status without checking two teams, two test suites, and a pile of edge cases. A sales update can break support screens. A support rule can confuse reporting. The model gets bigger, but nobody gets a cleaner system.
Two local models are often cheaper. Sales keeps its own status model. Support keeps its own status model. When they need to talk, they use a small mapping based on shared facts like unpaid invoices, account blocks, or support tier.
A real flow might look like this: sales marks an account as past due because payment failed. Support does not need a new shared status for that. Support can map "past due" plus "high contract tier" to "waiting on finance" and still treat the customer as VIP for urgent issues.
That split costs less when the two meanings change at different speeds. Share the facts both teams truly own. Let each team name the status it needs.
How to keep the choice under control
The first version usually looks fine. Trouble shows up later, when one domain changes every week and the other barely moves. That is when shared code starts pulling teams together, or copied code starts drifting in ways nobody tracks.
If you keep a shared kernel, keep it boring. Put only stable rules in the middle: shared terms, a few validation rules, maybe small value objects. Leave fast changing workflows, feature flags, and deadline driven exceptions out of it. Shared code should change slowly, or it becomes a traffic jam.
Give that shared code one clear owner. Not a group chat and not two teams taking turns. One person or one team decides what goes in, what stays out, and when a change needs a new version. That alone cuts a lot of wasted time.
If you copy the code, treat each copy like a fork with a birth record. Add a short note in the file header, package docs, or repository notes that says where it came from and when you copied it. Later, if one copy gets a bug fix, you can find the related copies fast instead of guessing during an incident.
A simple routine helps either way. Review the shared part or copied versions on a fixed schedule, such as monthly or at each release. Check whether the shared code still holds only stable rules. Compare copies for fixes or rule changes that now need to stay aligned. Remove old flags, dead branches, and temporary adapters before people start depending on them.
Teams get into trouble when they skip that cleanup. A temporary branch stays in place. An old switch never gets removed. Soon a small decision turns into hidden coupling, and a tiny change needs several pull requests and extra testing.
Control comes from small habits: keep the center small, assign ownership, record where copies came from, and clean up old paths early.
Mistakes that create hidden coupling
Teams often create hidden coupling for a simple reason: two things look similar on paper, so they treat them as the same thing in code. Matching names are not enough. One team may use "account status" to decide billing while another uses the same words to drive support rules. The label matches. The meaning does not.
That mistake gets expensive when people put shared code in place too early. A small shared type turns into a shared rulebook, then shared validation, then shared release risk. Soon one team cannot change a field or rule without checking with the other.
Copied code can go wrong too. The common failure is not the copy itself. It is the lack of ownership after the copy. If nobody decides who updates the logic, the copies drift in random ways. Six months later, both teams think the other one owns the rule, and nobody trusts the result.
Another common problem is packing too much business logic into the shared part. Shared code should stay small and boring. Once teams move pricing rules, approval logic, or workflow steps into it, they tie both domains to the same choices. That creates arguments in planning, delays in testing, and messy rollbacks.
Release rhythm matters more than many teams expect. If one group ships daily and the other ships once a month, a shared kernel can force both into the same pace. That is rarely a good trade. The slower team becomes a brake, or the faster team starts working around the shared code in ugly ways.
Team coordination has a real cost, even when nobody puts it on a budget sheet. Meetings, reviews, chat threads, and waiting for answers all add up. A shared model that saves 30 lines of code can easily cost hours every week.
A quick sanity check helps:
- Do both teams mean the same thing, not just use the same name?
- Can each team change the rule without asking permission?
- Will both teams release on a similar schedule?
- Is there one clear owner for every shared rule?
If the answer is "no" more than once, copying the code is usually cheaper.
A quick check before you commit
Teams usually regret this choice when they answer one practical question too late: who has to wait when the rule changes? That delay is often more expensive than the code itself.
Use a short check with the people who own each domain. If the answers feel forced or vague, stop and dig deeper. That uncertainty is already part of the cost.
Ask whether both domains change this concept at about the same pace. Ask whether both sides need the change in the same release window. Ask whether one team can wait for the other without blocking work. Ask whether duplicated logic will stay small for the next year. Ask whether one team can clearly own the shared rules.
The pattern is simple. A shared kernel is cheaper when both sides move together, release together, and accept one owner. Copying the code is cheaper when each side needs room to change on its own.
If the answers are mixed, pick the option that lowers coordination first. Teams often overrate the cost of duplication and underrate the cost of waiting. A small copy with a review date in six months is often safer than shared code with fuzzy ownership.
Write the decision down in one sentence. Include who owns the concept, how often you expect it to change, and what would make you switch later.
Next steps for your team
Make the decision concrete this week. Pick one concept that appears in two domains and write down three plain facts: what the concept means, who owns that meaning, and how often it changes. If the team cannot agree quickly, the boundary is still blurry.
Do not apply the choice everywhere at once. Test it on one small feature first. A simple case like customer status in billing and support is enough to show whether a shared kernel or copied code causes less pain.
For one or two releases, track who requested each change, which teams had to coordinate, how many places changed, whether one team had to wait, and which rules drifted apart. Then review the actual work, not opinions from the design meeting. If both domains moved at a similar pace and the concept stayed stable, sharing may be fine. If small edits caused delays, extra reviews, or surprise breakage, copying was probably the cheaper call.
When a team keeps reopening the same boundary, the model usually needs help. Another round of debate rarely fixes that. An outside architecture review often does because someone with distance can spot hidden coupling faster.
That is the kind of problem Oleg Sotnikov works on through oleg.is as a Fractional CTO and startup advisor. He helps startups and small businesses sort out domain boundaries, ownership, and architecture choices before the wrong model hardens into code.
Frequently Asked Questions
What is a shared kernel in plain terms?
A shared kernel is a small piece of domain code that two domains own together. Both sides use the same rules, names, and tests because they truly mean the same thing.
It only works when the concept stays stable and both teams can agree on changes without much delay.
When should we copy the code instead of sharing it?
Copy the code when the concept changes at different speeds in each domain. If one team edits it often and the other rarely touches it, local copies usually cost less.
That gives each team room to change rules without waiting for approval from the other side.
How do I tell if two teams really mean the same thing?
Ignore the name for a moment and compare the rules. Ask what decisions the concept drives, what fields matter, and what edge cases each team handles.
If those answers differ, the meaning already split even if the label stayed the same.
Is duplicated code always a bad design choice?
No. Duplication can be the cheaper choice when it removes constant meetings, reviews, and release waits.
Bad design starts when teams pretend two different meanings are one shared model just because the code looks tidy.
What should stay inside a shared kernel?
Keep only stable rules in it. Small value objects, shared terms, and simple validation usually fit.
Leave workflow steps, temporary exceptions, feature flags, and team-specific logic outside. Once those enter the shared part, friction grows fast.
How small should a shared kernel be?
Make it small enough that both teams can explain it in a few sentences. If it starts pulling in helpers, process logic, or lots of special cases, it grew too far.
A narrow shared part stays easier to trust and easier to change.
What if one team ships weekly and the other ships monthly?
Different release schedules often make shared code expensive. The faster team ends up waiting, or the slower team absorbs changes it does not need.
If one group ships every week and the other ships once a month, copied code usually causes less pain.
How can two local models talk to each other without one shared status?
Let each team keep its own model and share only the facts that both sides trust. Then add a small mapping between those facts and each local status.
For example, billing can send facts like unpaid invoice or account block, and support can map them to its own status rules.
How do we handle bug fixes after we copy the code?
Treat each copy like a fork with a clear origin. Write down where it came from and who owns it now.
When you fix a bug in one copy, check the related copies right away. A short review on each release also helps catch drift before it turns into a production issue.
When should we ask for an outside architecture review?
Bring in outside help when the team keeps reopening the same boundary, small changes trigger cross-team debates, or nobody can name a clear owner. Those are signs that the model and the ownership do not match.
A Fractional CTO or architecture advisor can review the domains, trim hidden coupling, and help the team pick a cleaner split before more code piles up.