Defects cluster: the 80/20 principle applied to testing
80% of bugs are concentrated in 20% of the code. Understanding the defect clustering principle lets you focus testing where it actually matters.

Every team has one, that module that always causes trouble. It doesn't matter how many times you fix it, it always breaks again. In every retrospective, someone says it should be rewritten, but there's never time. Support tickets pile up around the same screens, the same flows, the same three files nobody wants to touch. If this sounds familiar, it isn't a coincidence. There's a principle that explains why this happens, and understanding it can change how your team spreads its testing effort.
The defect clustering principle
ISTQB defines it as its fourth fundamental principle: “Defect clustering”. The wording is straightforward. A small number of modules usually contains most of the defects found during pre-release testing, or is responsible for most operational failures in production.
This behavior isn't unique to software. The Pareto principle, formulated by the Italian economist Vilfredo Pareto in the late 19th century, describes a pattern of uneven distribution that shows up across very different fields. In testing, the best-known version says that 80% of defects are concentrated in 20% of the code. The exact percentages vary from project to project, but the trend is consistent: bugs aren't distributed evenly.
Capers Jones, one of the most prolific researchers in software metrics, documented this pattern by analyzing thousands of projects over several decades. His data confirms that defect density varies enormously between modules within the same system, and that the most problematic modules tend to stay that way over time.
Why defects cluster
It isn't bad luck. There are technical and organizational reasons why certain modules attract defects like magnets.
- High cyclomatic complexity is one of the most direct factors. A module with lots of conditional branches, nested loops, and possible execution paths has more surface area for defects to hide in. It's pure arithmetic, more paths mean more combinations to validate and more chances for something to fail.
- Legacy code without tests is a classic. Nobody wants to touch it because there's no safety net, so every change is a gamble. Patches pile up on top of each other and technical debt grows until the module becomes a permanent risk zone.
- High developer turnover in certain modules creates a context problem. Each person who passes through the code understands one part, adds their own logic, and leaves. Over time, nobody has a complete view of how the whole thing works, and undocumented interactions between components become a constant source of bugs.
- Integrations with external systems concentrate defects because they depend on contracts that can change, services with variable latency, and data formats that don't always match the spec. The module that talks to three external APIs will always be more fragile than the one that only reads from a local table.
- Areas with changing requirements accumulate defects simply because they're modified more often. Every change is a chance to introduce a regression, and the modules that get touched the most are the ones that suffer the most regressions.
Examples you'll recognize
The most universal one is the signup form. In almost every product I've worked on, the signup and login flow is one of the areas with the highest concentration of tickets. Email validation, password policies, recovery flows, OAuth integration, two-step verification, it all piles up in one place that also happens to be the user's entry point. If it fails there, there's no product.
Another classic is the billing or payments module. It combines complex business logic with external integrations, changing tax rules, multiple currencies, discounts, taxes, and edge cases that show up at the worst possible time. On one project I worked on, 40% of production bugs over six months came from the billing module, which represented less than 10% of the total codebase.
And then there's the code nobody wants to touch. That two-thousand-line file that hasn't had a serious refactor in three years, written by someone who no longer works at the company, with cryptic comments and a structure that resists any attempt at quick understanding. Everybody knows it's a problem, but the perceived cost of fixing it always seems higher than the cost of keeping it patched together.
Common mistakes when you ignore this principle
The most common mistake is spreading testing effort evenly across the whole system. If you spend the same amount of time testing the "About" module as the payments engine, you're wasting resources. Defects aren't distributed evenly, and your testing effort shouldn't be either.
Another common problem is not analyzing the data you already have. Most teams have some kind of bug tracking system, whether it's Jira, Linear, GitHub Issues, or anything else. That data contains valuable information about where problems are concentrated, but surprisingly few teams analyze it systematically. They fix the bug, close the ticket, and move on to the next one without asking whether there's a pattern.
It's also common to confuse symptoms with causes. If a module has lots of reported bugs, that might mean it has lots of defects, sure, but it might also mean it's the most heavily used and therefore the most observed. Or that the users of that module are more demanding or more likely to report problems. Before you reassign testing effort, it's worth understanding why defects are concentrated there.
Finally, some teams fall into the trap of only testing hotspots. The principle says defects cluster, not that modules with no known defects are bug-free. If you completely abandon testing in the rest of the system, you're creating new blind spots. The right balance is to spend more effort on high-risk areas without neglecting everything else.
How to apply it in your team
Analyze your bug data
The first step is knowing where defects are concentrated in your project. Pull the bugs from the last six months out of your tracking tool and group them by module, feature, or component. You don't need sophisticated tools for this, a spreadsheet is enough. Sort by frequency and the hotspots will start to show up. Almost every time, two or three areas account for most of the problems.
If you want to go one step further, combine that information with severity. Ten cosmetic bugs in one module isn't the same as three critical bugs in another. An analysis that combines frequency and severity will give you a much more useful picture of where to focus your effort.
Use hotspot detection tools in the code
CodeScene is probably the best-known tool for this. It analyzes Git history and combines change frequency with code complexity to identify files that are both complex and heavily modified. Those are the highest-risk areas. Other tools like SonarQube also offer complexity and maintainability metrics that help identify problematic modules.
The point is not to depend only on the team's intuition to know where the problems are. Repository data contains objective signals that complement people's experience.
Prioritize testing based on risk
Once you know your hotspots, adjust how you distribute testing effort. Modules with high defect density, high complexity, or critical business importance should get more attention, more automated tests, more exploratory testing, more thorough code reviews.
In practice, that might mean the payments module gets 85% test coverage and monthly exploratory testing sessions, while the "Frequently asked questions" page gets a basic smoke test. It's not that one is more important than the other in the abstract, it's that defect history and business impact justify an uneven allocation.
Defect density metrics by module
Add a simple metric to your retrospectives or quality reports: number of defects per module in the last cycle. You don't need to make the formula more sophisticated than that at first. Over time you can normalize it by lines of code or by number of changes, but the real value is in making the distribution visible and watching how it evolves.
If a module consistently shows up near the top, that's a clear sign it needs intervention. Maybe more tests, maybe a refactor, maybe both. But the first step is making it visible so the team can make informed decisions.
Invest in the root cause, not just more tests
Sometimes the problem isn't missing tests, it's that the code is inherently fragile. A module with a cyclomatic complexity of 50 is going to keep generating defects no matter how much you test it, because the interactions between its branches are too many to cover fully. In those cases, the most effective long-term fix is to refactor the module to reduce its complexity, ideally with tests that protect the refactor.
In my experience, convincing the team to spend time refactoring a problematic module is much easier when you can show specific data: “This module has generated 23 bugs in the last three months and accounts for 35% of our debugging time. Spending two sprints refactoring it will save us that cost over and over again”. Data turns an opinion into a business argument.
The data tells you where to look
The defect clustering principle isn't a theoretical curiosity. It's a practical tool for making better decisions about where to invest testing time and effort. Defects aren't distributed randomly, and your testing strategy shouldn't be random either.
Knowing your hotspots lets you be more efficient with limited resources, because there's always more functionality to test than time available. The key is using the data you already have to focus effort where it has the biggest impact.
If you can only do one thing this week, do this: pull the bugs from your project from the last three to six months, group them by module, and see what pattern appears. I'd bet 80% of the problems come from the same handful of places. Once you see it in the data, the decisions about where to focus testing almost make themselves.
Fourth principle of the seven ISTQB testing principles. You came from Testing early saves time and money and next up is The pesticide paradox.

Jose, author of the blog
QA Engineer. I write out loud about automation, AI and software architecture. If something here helped you, write to me and tell me about it.
Leave the first comment
What did you think? What would you add? Every comment sharpens the next post.
If you liked this
Variables de entorno en scripts E2E: secretos seguros en JMO Labs
Los scripts E2E necesitan datos sensibles —tokens de API, credenciales, URLs privadas— sin que aparezcan en el código. En JMO Labs hemos añadido variables de script con modo privado: se inyectan automáticamente, se enmascaran en los logs y se acceden con una sintaxis limpia.

Tests E2E que se reparan solos: cómo construimos un pipeline de self-healing con IA
Los tests E2E se rompen con cada cambio de interfaz. En JMO Labs construimos un pipeline de 5 fases con IA que planifica, ejecuta, repara selectores, diagnostica fallos y verifica resultados de forma autónoma. La caché de selectores hace que cada ejecución sea más rápida que la anterior.

Construir una plataforma de testing con Playwright: arquitectura de JMO Labs
Playwright no es solo para tests E2E. En JMO Labs lo usamos como motor completo: 9 fases de comprobación, localizador de 9 estrategias con self-healing, grabación de vídeo, testing responsive con viewports reales y accesibilidad con axe-core.