TL;DR — Greenfield means you start from an empty repo and your main risk is building the wrong thing. Brownfield means you inherit a running system and your main risk is breaking something nobody remembers depends on it. The skills overlap, but the default moves are opposite.
Definitions
The terms come from urban planning and construction:
- Greenfield: land that has never been developed. No foundations, no pipes, no contamination.
- Brownfield: land previously used for industrial purposes. You can build on it, but first you deal with what’s already in the ground.
In software (Wikipedia: Greenfield project, Wikipedia: Brownfield (software development)):
| Greenfield | Brownfield | |
|---|---|---|
| Starting point | Empty repository | Existing codebase in production |
| Users | None (yet) | Real users, real data, real SLAs |
| Main constraint | Unknown requirements | Existing behavior and integrations |
| Main risk | Building the wrong thing | Breaking something that works |
| Architecture | Chosen | Inherited |
| Feedback loop | Slow (no users) | Fast but noisy (prod incidents) |
| Typical mistake | Over-engineering | Big-bang rewrite |
Most real projects are a mix. A new microservice inside an old platform is greenfield code in a brownfield environment: it has to speak the existing auth, the existing message format, the existing deployment pipeline.
Greenfield: freedom is the problem
An empty repo looks like the easy case. It isn’t — it just moves the difficulty.
What you get
- Choice of language, framework, infra, data model.
- No backward compatibility contracts.
- Clean CI/CD, IaC, observability from day one — if you actually set them up.
What goes wrong
1. Designing for scale you don’t have. Kubernetes, event sourcing, and five microservices for a product with zero users. Every abstraction you add before you have a real use case is a guess, and you will pay for each wrong guess later.
Gall’s Law applies directly: a complex system that works evolved from a simple system that worked. Start with the simplest thing that ships.
2. Unknown requirements disguised as known ones. The spec is a hypothesis. The first real users will invalidate parts of it. Optimize for cheap change, not for the design on the whiteboard.
3. Skipping the boring foundations. The things that are trivial to add on day 1 and painful on day 300:
# Day-1 checklist that's nearly free now and expensive later
- Linting + formatting enforced in CI
- Tests running on every PR
- Infrastructure as code (no click-ops)
- Structured logging + a metrics endpoint
- Reproducible local env (devcontainer / docker compose)
- Secrets out of the repo from commit #1
- Architecture Decision Records (ADRs)
ADRs are worth calling out: in a greenfield project you are the person who knows why every decision was made. In two years someone else will inherit it as brownfield. Write it down (Michael Nygard: Documenting Architecture Decisions).
Greenfield default moves
- Monolith first, modular inside (Martin Fowler: MonolithFirst).
- Boring, well-documented technology.
- Ship a thin vertical slice to real users as early as possible.
- Automate the pipeline before the codebase gets big.
Brownfield: the system is the spec
When you inherit a running system, the code is not just an implementation of requirements. It is the requirements — including the ones nobody wrote down.
What you get
- Real users and real data telling you what matters.
- Proven business value (it’s still running for a reason).
- Existing infra, integrations, and operational knowledge.
What goes wrong
1. Hidden contracts. Some downstream job parses a log line. A partner relies on a field being null instead of missing. A report depends on a sort order that was never guaranteed. This is Hyrum’s Law: with enough users, every observable behavior of your system will be depended on by somebody.
2. No tests, so no safe changes. Michael Feathers defines legacy code simply as code without tests (Working Effectively with Legacy Code, Prentice Hall, 2004). Without tests you can’t refactor; you can only edit and hope.
3. The rewrite trap. “It’s a mess, let’s start over.” The rewrite becomes a greenfield project that must reach parity with a moving target, while the old system keeps getting patched. Joel Spolsky’s classic essay on this is still accurate (Things You Should Never Do, Part I).
Brownfield default moves
Map before you touch. Dependencies, data flows, cron jobs, who calls what. Read the deploy pipeline and the runbooks before the code.
Pin current behavior with characterization tests. Not “what should it do” but “what does it do right now”:
# Characterization test: lock in existing behavior, even if it looks wrong
def test_invoice_total_rounding_current_behavior():
# Legacy code rounds each line item, not the total.
# Downstream reconciliation depends on this. Do not "fix" without a plan.
assert calculate_invoice_total([10.005, 10.005]) == 20.02
Change incrementally, behind seams. Two patterns cover most cases:
- Strangler Fig — route traffic through a facade, replace functionality piece by piece, retire the old system when nothing calls it anymore (Martin Fowler: StranglerFigApplication, Microsoft Azure: Strangler Fig pattern, AWS Prescriptive Guidance: Strangler fig).
- Branch by Abstraction — introduce an interface in front of the component, build the new implementation behind it, switch over with a flag (Martin Fowler: BranchByAbstraction).
┌──────────────┐
client ──▶ │ facade / │──▶ new service (routes migrated)
│ proxy │──▶ legacy app (everything else)
└──────────────┘
Make every change reversible. Feature flags, dark launches, shadow traffic, and a tested rollback. In brownfield, rollback speed matters more than deploy speed.
Improve observability first. You can’t safely change what you can’t see. Adding metrics and structured logs to the legacy system is usually the highest-leverage first PR.
Data engineering version
The same split shows up in data platforms:
| Greenfield data platform | Brownfield data platform | |
|---|---|---|
| Example | New lakehouse, no consumers yet | Migrating a 10-year-old warehouse |
| Schema | Designed upfront | Defined by existing reports and ETL |
| Hard part | Modeling for questions not asked yet | Parity: every dashboard must still match |
| Key technique | Contracts and conventions from day 1 | Parallel run + row/aggregate reconciliation |
For brownfield migrations, the parallel run is the strangler fig for data: both pipelines run, outputs are diffed, consumers move table by table once the numbers reconcile. Tools like dbt make this tractable through tests and versioned models (dbt: Data tests, dbt: Model contracts).
How to decide: rewrite or evolve?
When you’re in a brownfield system and someone proposes a greenfield replacement, check these:
- Can you list all consumers and integrations of the current system?
- Is the current behavior documented or covered by tests?
- Can the old and new system run in parallel?
- Is there a migration path that delivers value before 100% parity?
- Is the problem the code, or the requirements/organization (which a rewrite won’t fix)?
If most answers are “no”, evolve incrementally. A rewrite without those answers is a greenfield project with brownfield expectations — the worst of both.
Summary
- Greenfield: the risk is the unknown. Stay simple, ship early, set up the foundations that are cheap now and expensive later, document decisions for whoever inherits it.
- Brownfield: the risk is the known-but-undocumented. Observe first, pin behavior with tests, change incrementally behind seams, keep every step reversible.
- Every greenfield project becomes brownfield. The best thing you can do on day 1 is make life easier for the engineer who inherits it on day 1000 — which is usually you.
References
- Wikipedia — Greenfield project
- Wikipedia — Brownfield (software development)
- Martin Fowler — StranglerFigApplication
- Martin Fowler — BranchByAbstraction
- Martin Fowler — MonolithFirst
- Microsoft Azure Architecture Center — Strangler Fig pattern
- AWS Prescriptive Guidance — Strangler fig pattern
- Hyrum’s Law
- Michael Nygard — Documenting Architecture Decisions
- Joel Spolsky — Things You Should Never Do, Part I
- dbt — Data tests
- dbt — Model contracts
- Michael Feathers — Working Effectively with Legacy Code, Prentice Hall, 2004