Testing & Code Quality

[Code Smell #2] Lasagna Code: Why One Field Means Fixing Seven Files

Lasagna code is code where adding one field requires changing seven files despite neatly separated layers. This article summarizes why layers multiply, where they cost you, and when to keep or remove them.

6 min read
Cover image for [Code Smell #2] Lasagna Code: Why One Field Means Fixing Seven Files

The spaghetti code in the previous article had tangled control flow. Does perfectly整理ing the flow make it good code?

This continues from the previous article Code Smell #1.

Not necessarily.

You separate layers cleanly, make each layer call only the one below it, and draw boundaries with interfaces—yet the code still becomes something you hate touching.

Code that requires changing seven files just to display one string field. This is called Lasagna Code (C2 original).

Code with neatly stacked layers

It is exactly what the name suggests. Lasagna is made by stacking pasta and sauce in layers.

The cross-section is neat and the layers are distinct. The problem is that you cannot remove just one layer.

Here is a typical example. A nickname field was added to the user data returned by the server, and it needs to be displayed on screen.

1. UserDTO            — Add a field to the server-response struct
2. UserMapper         — DTOAdd one line to the code converting it into the domain model
3. User               — Add a property to the domain model
4. UserRepository     — Update this too if the protocol signature changes
5. FetchUserUseCase   — It only passes through, but the type gets in the way, so modify it
6. UserViewModel      — Convert it into the view model again
7. ProfileView        — Finally display it

From the DTO (Data Transfer Object) to the screen, the same string is stored in three different types.

Of the seven files, how many actually make a decision? Usually one or two.

The rest merely pass the value along unchanged.

These are called pass-through layers.

Architecture-pattern literature also calls a state where a request merely passes through layers without processing the Sinkhole Anti-Pattern. It is the defining symptom of Lasagna Code.

Why does this happen?

No one creates this with bad intentions. In fact, it is usually the opposite.

Layers multiply mostly because good advice is applied without context.

When “separate the layers” is treated as a rule. People see a Clean Architecture diagram and create as many folders as there are circles.

The diagram does not prescribe how many layers you must have. It illustrates the principle that dependencies should point inward only (Presentation-Domain-Data Layering).

But its meaning changes when translated into a folder structure.

When “depend on abstractions, not implementations” is applied everywhere. You end up with piles of protocols that have only one implementation.

UserRepository One protocol and UserRepositoryImpl one implementation. All the protocol does is add one more code jump.

That is worthwhile when you need to insert a fake implementation in tests. Without that plan, it simply adds another layer.

When preparing in advance for “being able to change it later.” You abstract the database in case it changes and add another wrapper in case the server changes.

This is exactly the situation YAGNI (You Aren’t Gonna Need It) warns about.

When the team grows and is split into layers. As Conway’s Law says, an organization’s communication structure is engraved directly into its architecture.

With four teams, you are likely to get four layers, whose boundaries follow people more than technical needs.

A diagram of the seven-layer modification path from DTO to screen when adding one field
The red boxes are pass-through layers that make no decisions

Where do the costs come from?

Let us identify specifically what is bad about having many layers.

Change cost grows with the number of layers. Seven files for one field. More alarming than that is that developers try to avoid it.

Instead of passing through every layer properly, a shortcut appears: calling the API directly from the ViewModel.

When rules are tedious to follow, code that bypasses them appears. Eventually the layers remain, while the flow becomes spaghetti. Lasagna gives birth to spaghetti.

Reading becomes expensive. To learn where a value came from, you must trace down through the layers.

Each layer is short and clear, but after seven jumps you forget what the original question was.

Debugging slows down. The stack trace gets longer, and finding the layer where a value went wrong requires breakpoints at every layer.

Builds slow down. This is especially true when layers are split into modules. Every change to a lower layer rebuilds all upper layers.

When layers are needed—and when they are not

That does not mean you should eliminate layers. You need criteria for deciding.

One question usually works well.

“Does this layer make a decision, or does it only pass something through?”

Making a decision means transforming, validating, branching, or composing. A mapper that converts a server date string to Date makes a decision.

A repository that uses the cache when available and the network otherwise also makes a decision. A UseCase that receives parameters and passes them on unchanged makes none.

Another criterion is the reason for change. A change to the server response format and a change to display rules have different reasons.

That makes separating DTOs and view models worthwhile. Conversely, if the domain and view models always change together, there is no reason to keep them separate.

Here is the condensed version in a table.

Situation Should you add a layer?
The external response format and internal model evolve independently Yes
There is a real plan to swap implementations Yes
A fake implementation is needed in tests Yes
The scale requires encoding team boundaries in code Conditionally
There is only one implementation and there always will be No
It only receives and forwards values unchanged No
You created it because the diagram had that layer No
An image comparing layers that transform values with pass-through layers
A transformation is a layer; passing values through unchanged is clutter

How to remove layers

A rough order for dealing with an already-built lasagna is as follows.

Find pass-through layers first. If a method body is one line and that line calls a same-named method on another object, it is a candidate.

Searching the whole project for this pattern quickly produces a list.

Count the implementations. Check how many implementations each protocol has. If there is only one and tests do not use it, delete the protocol and use the concrete type directly.

It is best not to justify this with performance. any UserRepositoryCalling through the same existential type does go through a witness table.

However, using the protocol as a generic constraint enables specialization and removes that cost. Conversely, a class that is not final still uses dynamic dispatch by default even when called through its concrete type.

Besides, this difference is not measurable in a repository call that crosses a network or database boundary.

The reason to remove a protocol is not performance but the number of jumps.

Merge types. If a DTO and domain model have identical fields and always change together, use one type.

Some people consider attaching Codable directly to the domain model contamination. But first ask when that contamination actually becomes a problem.

Fold it into one layer. If deleting a layer feels too risky, keep the layer but merge the files.

Putting the protocol and implementation in one file alone reduces the number of jumps.

Summary

  • Lasagna Code has so many layers that even a small change requires editing multiple files.
  • It usually comes not from writing bad code, but from applying good advice without context.
  • The key symptom is the pass-through layer: a layer that decides nothing and merely forwards values.
  • There are two criteria: does this layer make decisions, and does it change for a reason different from the other layers?
  • When rules are tedious to follow, shortcuts appear. With too many layers, spaghetti eventually grows alongside them.

The next article is about cases where the pieces, not the layers, are the problem. We will look at Ravioli Code: every class is small and well encapsulated, but no one can explain the overall flow.

Sources and verification criteria

  • Lasagna Code — C2 Wiki · original by the author · verified 2026-08-17 · basis: the Lasagna Code metaphor and the problem of excessive layering
  • Presentation-Domain-Data Layering — Martin Fowler · original by the author · verified 2026-08-17 · basis: the purpose of layer separation and change boundaries

Continue reading