Quick Answer

The test pyramid says to have many fast, isolated unit tests, fewer integration tests, and only a handful of slow end-to-end tests, because speed, isolation, and realism trade off against each other. Getting this backwards, known as the ice-cream-cone anti-pattern, produces a slow, fragile suite that people stop running.

The shape, and why it exists

The test pyramid is a shape, not a formula: many small, fast tests at the bottom, a smaller number of broader tests in the middle, and a handful of full end-to-end tests at the top. Each layer trades cost for realism in the opposite direction from the one below it.

Unit tests check one function or one small unit of logic in isolation, with no real database, network, or file system, just the code and its inputs. They run in milliseconds and pinpoint failures precisely: if a unit test fails, you know almost exactly which function is wrong. Integration tests check that two or more real pieces work together, a service talking to a real (or realistically scoped) database, an API handler wired to real middleware, catching bugs that live in the seams between components. End-to-end tests drive the whole system the way a real user would, through a browser hitting a real deployed stack, catching the class of bug that only exists when every layer is real at once.

The shape exists because speed, isolation, and realism trade off against each other, and no single layer can deliver all three at once.

The cost difference, measured for real

To make the cost difference concrete rather than theoretical, here is a real measurement: two trivial tests asserting the exact same fact, that 2 + 3 equals 5, written two different ways. One is a pure unit test. The other spins up a real Node HTTP server, makes a real socket connection to it, parses a real JSON response, and shuts the server down, a miniature version of what an end-to-end test does against a real backend.

unit: add() computes a sum                    3ms
e2e: a real HTTP round trip to a live server  30ms

Run it again and the numbers move slightly (2ms and 20ms on a second run) but the ratio holds: roughly ten times slower, for the cheapest possible version of an end-to-end test, no browser, no real network, no database, everything in one process on one machine. A real end-to-end test driving a browser against a deployed environment is routinely 100 to 1,000 times slower than its unit equivalent, not 10x. Multiply either number by a few hundred tests and the reason pyramids point the way they do stops being a style preference and becomes arithmetic.

The ice-cream-cone anti-pattern

The inverted version of the pyramid, few or no unit tests, a moderate number of integration tests, and a large pile of slow, brittle end-to-end tests, is common enough to have its own name: the ice-cream-cone anti-pattern. It usually happens by accident, not by decision: teams under deadline pressure write end-to-end tests first because they are the most convincing demo of "it works," and never circle back to add the fast, isolated tests underneath.

The cost shows up gradually. A full suite that takes 45 minutes instead of 45 seconds means feedback arrives too late to be useful during development, so people stop running it locally and find out something broke only after pushing. End-to-end tests are also the layer most exposed to real timing and real network calls, so a cone-shaped suite tends to also be an unreliable one, compounding slowness with false failures that erode trust in the whole pipeline. And when an e2e test fails, it tells you that something broke somewhere in a five-layer stack, not which layer, exactly the diagnostic precision a unit test gives you for free.

What actually belongs at each layer

In practice, the decision comes down to what you are trying to catch. Pure business logic, discount calculations, validation rules, parsing, anything expressible as "given this input, expect this output" with no I/O, belongs in unit tests, because isolation is what makes them fast and precise, and there is no reason to pay for a database connection to check a branch of an if statement.

Integration tests earn their cost at real boundaries: a repository layer against an actual test database, so a query that is syntactically valid but semantically wrong gets caught, or an API route wired through real middleware, so an auth check that looks fine in isolation but breaks when combined with a real request pipeline gets caught. These are more expensive than unit tests but catch a category of bug unit tests cannot, by design.

End-to-end tests should be reserved for a short list of journeys where the business genuinely cannot tolerate a break: sign-up, login, checkout, whatever flows would be a company-wide incident if they silently stopped working. Everything else that seems like it "should" be end-to-end usually has a cheaper equivalent one or two layers down.

70/20/10 is a default, not a law

A commonly cited default is roughly 70% unit, 20% integration, 10% end-to-end, useful as a sanity check, not as a rule to enforce with a linter. The right ratio for a given codebase depends on where its actual risk lives: a stateless, calculation-heavy service leans harder into unit tests, while a system whose main risk is in how components integrate, message queues, multiple services, complex auth, reasonably carries more integration tests than the textbook ratio suggests.

The one number worth watching is not any specific percentage but the total suite runtime, and how often each layer actually catches something. If the end-to-end layer keeps catching bugs the integration layer should have caught, that is a signal to invest one layer down rather than to add more end-to-end coverage. The pyramid describes a healthy shape, not a target to hit by counting tests until the ratio matches; a suite with the right ratio but weak assertions everywhere is no better off than a suite that only chases 100% coverage.

Frequently Asked Questions

What is the test pyramid? A model for how many tests to have at each layer: many fast unit tests, fewer integration tests, and a handful of end-to-end tests, because speed, isolation, and realism trade off against each other.
Why not just write end-to-end tests, since they test the real thing? They are slow and comparatively fragile, measured here at roughly 10x a unit test for the simplest possible case, and 100-1000x for a real browser-driven test, so a suite built mostly from them becomes too slow to run often.
What is the "ice-cream-cone" anti-pattern? The inverted pyramid, few unit tests and mostly slow end-to-end tests, that happens when teams write convincing end-to-end demos under deadline pressure and never add fast tests underneath.
Is the 70/20/10 ratio a rule I need to follow exactly? No. It is a sane default, not a target. The right ratio follows where a codebase's actual risk lives, not a fixed formula applied to every project.
What should always be an end-to-end test? Only the handful of journeys where a silent failure would be a real incident, such as sign-up, login, or checkout, not every user-facing feature in the product.