A code smell is a surface-level indicator that something deeper is wrong with your software design. It is not a bug. The code compiles, the tests pass, and the feature works. But the structure signals that future changes will be harder, slower, and riskier than they need to be. Learning to recognize code smells is one of the most valuable skills a developer can build, because catching a design problem early prevents weeks of painful refactoring later.
Martin Fowler popularized the term in his 1999 book Refactoring, and the concept has only grown more relevant as codebases grow larger and teams grow more distributed. This guide catalogs the 20 most common code smells, groups them by category, and explains how to detect and fix each one.
What Is a Code Smell
A code smell is a pattern in source code that suggests a violation of fundamental design principles. The smell itself is not the problem. It is a symptom that points toward a deeper structural issue.
Consider a method that takes 12 parameters. The code works, but the long parameter list signals that the method is doing too much, or that the parameters should be grouped into an object. The smell (too many parameters) points to the disease (poor abstraction).
Key characteristics of code smells:
- They are subjective, not absolute. Context determines severity. A 100-line method in a mathematical computation might be appropriate. A 100-line method in an HTTP controller almost certainly is not.
- They are heuristics, not rules. A 200-line method in a parser may be justified. A 200-line method in a controller is almost never justified. Smells guide attention, not mandate action.
- They compound. One smell attracts others. A God Class breeds Feature Envy, which breeds Shotgun Surgery. Left unchecked, a single smell can degrade an entire module.
- They increase the cost of change. Every smell adds friction to future modifications. What should be a one-file change becomes a five-file change. What should take an hour takes a day.
According to a 2023 study published in the IEEE Transactions on Software Engineering, codebases with high smell density experience 40% more defects per release than codebases with low smell density. Smells do not cause bugs directly, but they create the conditions where bugs thrive.
The goal is not zero smells. The goal is awareness. When you recognize a smell, you can make an informed decision: fix it now, fix it later, or accept the tradeoff. Awareness turns smells from hidden risks into managed tradeoffs.
The 20 Most Common Code Smells
The following catalog organizes smells into five categories based on the original taxonomy by Fowler and Mika Mantyla. Each smell includes a brief description, a recognition pattern, and a refactoring response.
Bloaters
Bloaters are code constructs that have grown so large they become difficult to understand and maintain. They tend to accumulate gradually as features pile up without restructuring. Bloaters are the most common category because growth is natural and restructuring takes deliberate effort.
1. Long Method
A method that contains too many lines of code. Long methods are hard to read, hard to test, and hard to reuse. If you need a comment to explain what a block of code does, that block should probably be its own method.
Recognition: Methods longer than 20 to 30 lines, multiple levels of nesting, inline comments explaining sections. Methods that require scrolling to read entirely.
Fix: Extract Method. Pull each logical block into a named method that communicates intent. The original method becomes a summary of high-level steps.
2. Large Class (God Class)
A class that has too many responsibilities. It knows too much and does too much. God Classes violate the Single Responsibility Principle and become magnets for change. Every feature seems to require modifying the God Class, and merge conflicts become routine.
Recognition: Classes with 500+ lines, dozens of fields, methods that span multiple domains. Classes that show up in version control diffs for nearly every feature branch.
Fix: Extract Class. Group related fields and methods into focused, cohesive classes. A God Class named UserManager might split into UserAuthentication, UserProfile, and UserNotifications.
3. Primitive Obsession
Using primitive types (strings, integers, arrays) instead of small objects for simple tasks. Representing a phone number as a string, a money amount as a float, or a date range as two separate fields.
Recognition: Validation logic scattered across multiple methods. String parsing repeated in several places. Constants used for type coding. Methods that take a string parameter but only accept specific formats.
Fix: Replace Data Value with Object. Create a PhoneNumber, Money, or DateRange class that encapsulates behavior with data. The new class owns its own validation and formatting logic.
4. Long Parameter List
A method that takes too many parameters. Long parameter lists are hard to read, easy to get wrong (especially with multiple parameters of the same type), and signal that the method needs too much context to do its work.
Recognition: Methods with more than three or four parameters. Callers that pass null for unused parameters. Boolean parameters that change method behavior.
Fix: Introduce Parameter Object or Preserve Whole Object. Group related parameters into an object. If the parameters come from another object, pass the object itself instead of extracting individual fields.
5. Data Clumps
Groups of data that appear together in multiple places. If the same three fields show up in several classes or method signatures, they belong together in their own object.
Recognition: The same set of fields repeated across classes. Method signatures that share parameter subsets. Configuration values that always travel together.
Fix: Extract Class for the clump. Replace field groups with references to the new class. This often reveals missing domain concepts that were hidden behind raw data.
Object-Orientation Abusers
These smells indicate incorrect or incomplete application of object-oriented principles. They often appear when developers use OOP syntax without applying OOP design thinking.
6. Switch Statements
Repeated switch or if-else chains that branch on type codes. Every time a new type is added, every switch statement must be updated. This violates the Open/Closed Principle.
Recognition: Switch statements that appear in multiple methods, branching on the same condition. A new feature requires modifying five different switch statements.
Fix: Replace Conditional with Polymorphism. Create a class hierarchy where each subclass handles its own behavior. The switch statement becomes a method dispatch.
7. Temporary Field
A class field that is only set and used under certain conditions. The rest of the time it sits empty or null, confusing anyone reading the code. Readers cannot tell which fields are always available and which are conditionally populated.
Recognition: Fields that are null or unused for most of the object's lifetime. Conditional logic that checks whether a field has been set before using it. Fields initialized to sentinel values.
Fix: Extract Class. Move the temporary field and its related methods into a separate class that only exists when the data is relevant.
8. Refused Bequest
A subclass inherits methods and fields from a parent but does not use most of them. The subclass refuses its inheritance. This signals that the inheritance hierarchy does not model the domain correctly.
Recognition: Subclasses that override parent methods with empty implementations or throw UnsupportedOperationException. Subclasses that ignore most inherited fields.
Fix: Replace Inheritance with Delegation, or restructure the hierarchy so the subclass only inherits what it needs. Favor composition over inheritance when the "is-a" relationship does not hold.
9. Alternative Classes with Different Interfaces
Two classes that do the same thing but have different method signatures. This makes it impossible to substitute one for the other and prevents polymorphism.
Recognition: Classes with methods that take different parameter types or names but produce equivalent results. Multiple implementations of the same concept that cannot be used interchangeably.
Fix: Rename Methods and harmonize signatures. Consider extracting an interface or a shared superclass. This enables polymorphism and reduces duplication.
Change Preventers
These smells make code changes disproportionately expensive. A small feature request requires touching dozens of files. Change preventers are the most expensive smells in practice because they multiply the cost of every future modification.
10. Divergent Change
A single class that changes for multiple unrelated reasons. When you need to modify the same class for both a UI change and a database change, the class has divergent responsibilities.
Recognition: A class that appears in version control diffs for unrelated features. Multiple developers editing the same file for different reasons in the same sprint. A class with methods that belong to different layers of the architecture.
Fix: Extract Class. Split the class along responsibility boundaries. Each resulting class should change for one reason only.
11. Shotgun Surgery
The opposite of Divergent Change. A single logical change requires editing many classes across the codebase. Adding a new field to a data model requires changes in the model, the repository, the service, the controller, the serializer, and the test.
Recognition: Small feature changes that touch 10+ files. Fear of making changes because of unknown ripple effects. New team members asking "did I change everything I needed to?" after every modification.
Fix: Move Method and Move Field to consolidate related logic. Inline Class if a class exists only to delegate. Understanding the blast radius of changes is critical, and tools that provide call graph analysis and dependency mapping make this visible before changes are made.
12. Parallel Inheritance Hierarchies
Every time you create a subclass of one class, you must also create a subclass of another class. The two hierarchies grow in lockstep. This is a special case of Shotgun Surgery applied to class hierarchies.
Recognition: Class names that share prefixes or suffixes (e.g., OrderProcessor and OrderValidator, PaymentProcessor and PaymentValidator). Adding a new type requires creating classes in two or more packages.
Fix: Move Method to eliminate one hierarchy. Use delegation or composition instead of parallel inheritance. Collapse the hierarchies by combining related responsibilities.
Dispensables
Dispensables are code elements that could be removed without losing functionality. Their presence adds noise and cognitive load. They make the codebase larger without making it more capable.
13. Comments (Deodorant Comments)
Comments that explain what code does instead of why. If the code needs a comment to be understood, the code should be rewritten to be self-explanatory. Comments that explain intent or warn about non-obvious consequences are valuable. Comments that narrate code are a smell.
Recognition: Comments that restate the code in English. Commented-out code blocks. Outdated comments that contradict the current implementation. Comments that say "TODO: fix this" from three years ago.
Fix: Rename variables and methods to communicate intent. Extract Method with a descriptive name. Delete commented-out code (version control remembers it). Preserve only comments that explain why a non-obvious decision was made.
14. Duplicate Code
The same code structure appears in two or more places. Duplicated code means duplicated bugs and duplicated maintenance effort. When a bug is fixed in one copy but not the other, the system behaves inconsistently.
Recognition: Copy-pasted blocks. Methods in different classes that have identical logic. Sibling subclasses with similar methods. Functions that differ only in one or two lines.
Fix: Extract Method for duplicates within a class. Extract Superclass or apply Template Method for duplicates across classes. Pull Up Method if the logic belongs in a parent. Applying clean code principles systematically prevents duplication from accumulating.
A 2024 analysis by SonarSource found that the average enterprise codebase contains 12% to 15% duplicated code. Reducing duplication to below 5% correlates with a measurable decrease in defect rates.
15. Dead Code
Code that is never executed. Unreachable branches, unused variables, methods with zero callers, and classes that nothing references.
Recognition: IDE warnings. Static analysis tool reports. Coverage reports showing 0% execution. Methods with no callers in the call graph.
Fix: Delete it. Version control preserves the history. Dead code is not insurance; it is clutter. It confuses readers, inflates metrics, and sometimes introduces security vulnerabilities through unused dependencies.
16. Lazy Class
A class that does not do enough to justify its existence. It may have started with a purpose but lost responsibility through refactoring. Every class adds cognitive overhead, so classes must earn their existence.
Recognition: Classes with only one or two trivial methods. Wrapper classes that add no behavior. Classes created "just in case" that never grew beyond a skeleton.
Fix: Inline Class. Move the class's remaining behavior into the class that uses it.
17. Speculative Generality
Code designed for future use cases that never arrived. Abstract classes with a single implementation. Parameters that are never varied. Hooks that nothing calls. Generality that nobody asked for and nobody uses.
Recognition: Unused abstractions. Type parameters that are always the same. Methods with names like handleFutureCase. Interfaces with a single implementation.
Fix: Collapse Hierarchy. Inline Class. Remove Parameter. Delete the speculative code and add it back when an actual requirement demands it. YAGNI (You Aren't Gonna Need It) is the guiding principle.
Couplers
Couplers represent excessive coupling between classes. Changes in one class force changes in another. Couplers make it difficult to understand, test, or modify code in isolation. Research published in the Journal of Systems and Software found that classes with high coupling are 3.2 times more likely to contain defects than loosely coupled classes.
18. Feature Envy
A method that uses more fields and methods from another class than from its own. The method "envies" the data of the other class and belongs there instead. Feature Envy violates the principle that behavior should live close to the data it operates on.
Recognition: Methods that call getters from another class repeatedly. Chains of accessor calls to compute a value. Methods that could be moved to another class without changing any of its parameters.
Fix: Move Method to the class whose data it uses. If only part of the method envies another class, Extract Method first, then move the extracted method.
19. Inappropriate Intimacy
Two classes that access each other's private fields or internal details. They are too tightly coupled, and changes to one break the other. This often happens between classes that were once a single class and were split without proper encapsulation.
Recognition: Classes that use reflection or friend access to reach internals. Bidirectional associations where one direction would suffice. Classes in different packages that import each other's internal types.
Fix: Move Method and Move Field to reduce coupling. Extract Class to create an intermediary. Replace bidirectional with unidirectional association. Define clear public interfaces between the classes.
20. Message Chains
A client asks one object for another object, then asks that object for yet another object, forming a chain: a.getB().getC().getD().doSomething(). The client is coupled to the entire chain structure. If any intermediate object changes its API, the client breaks.
Recognition: Long chains of method calls. Navigation through multiple objects to reach a value. Code that reaches deep into an object graph to perform an action.
Fix: Hide Delegate. Create a method on the first object that encapsulates the chain. The client calls one method, and the delegation happens internally.
Detecting Smells with AI
Manual code reviews catch smells, but they depend on reviewer experience and attention span. A senior developer spots a God Class instantly. A junior developer may not recognize the pattern until it has caused three incidents.
Automated tools have detected smells for decades (SonarQube, PMD, Checkstyle), but traditional tools rely on static rules and thresholds. They flag a method as "too long" at 50 lines without understanding whether the length is justified. They count parameters without considering whether the parameters are related.
AI-powered analysis adds context. Instead of counting lines, an AI tool can analyze the method's responsibilities, its callers, and its position in the call graph. It can distinguish between a genuinely complex algorithm that needs length and a controller method that should be decomposed.
Modern codebase intelligence platforms take this further by indexing every symbol, dependency, and call relationship across the full codebase. This lets you:
- Identify God Classes by measuring coupling and cohesion across the dependency graph.
- Find Feature Envy by analyzing which class's data a method accesses most frequently.
- Detect Shotgun Surgery by mapping which files change together in version control history.
- Spot Dead Code by tracing call graphs and identifying methods with zero callers.
- Surface Duplicate Code by comparing abstract syntax trees across the codebase.
Glue indexes your entire codebase and lets you ask questions like "Which classes have the most dependencies?" or "Which methods are never called?" in plain English. The platform's code health analysis surfaces complexity hotspots and ownership gaps that manual review would miss.
The combination of AI detection and human judgment produces the best results. AI finds the smells. Humans decide which smells matter and which refactoring approach fits the context.
A 2025 study by the Consortium for Information and Software Quality (CISQ) estimated that poor software quality cost U.S. organizations $2.41 trillion in 2024, with a significant portion attributable to accumulated code smells and technical debt.
Refactoring Patterns for Each Smell
Every smell has a corresponding refactoring pattern. The following table maps each smell to its primary fix:
| Smell | Primary Refactoring | Secondary Options |
|---|---|---|
| Long Method | Extract Method | Replace Temp with Query, Decompose Conditional |
| Large Class | Extract Class | Extract Subclass, Extract Interface |
| Primitive Obsession | Replace Data Value with Object | Replace Type Code with Subclasses |
| Long Parameter List | Introduce Parameter Object | Preserve Whole Object |
| Data Clumps | Extract Class | Introduce Parameter Object |
| Switch Statements | Replace Conditional with Polymorphism | Replace Type Code with Strategy |
| Temporary Field | Extract Class | Introduce Null Object |
| Refused Bequest | Replace Inheritance with Delegation | Push Down Method |
| Alternative Classes | Rename Method, Extract Interface | Move Method |
| Divergent Change | Extract Class | Split along responsibility |
| Shotgun Surgery | Move Method, Move Field | Inline Class |
| Parallel Inheritance | Move Method | Collapse Hierarchy |
| Deodorant Comments | Rename, Extract Method | Delete stale comments |
| Duplicate Code | Extract Method | Pull Up Method, Template Method |
| Dead Code | Delete | Verify via call graph |
| Lazy Class | Inline Class | Collapse Hierarchy |
| Speculative Generality | Collapse Hierarchy, Inline Class | Remove Parameter |
| Feature Envy | Move Method | Extract and Move Method |
| Inappropriate Intimacy | Move Method, Move Field | Extract Class |
| Message Chains | Hide Delegate | Extract Method |
The most effective refactoring approach is incremental. Fix smells as you encounter them during feature work rather than scheduling large refactoring sprints. This keeps the codebase clean without pausing feature delivery. The "Boy Scout Rule" applies: leave the code cleaner than you found it.
Safe refactoring requires tests. Before applying any refactoring pattern, verify that tests cover the code you are about to change. If tests do not exist, write them first. Refactoring without tests is surgery without imaging. You might succeed, but you cannot know until the patient wakes up.
Prioritize smells based on impact. A God Class in the authentication module (modified every sprint) matters more than dead code in a rarely touched utility. Focus on the smells that sit in your team's critical path.
Frequently Asked Questions
What is a code smell?
A code smell is a surface-level indicator in source code that suggests a deeper design problem. It is not a bug or an error. The code functions correctly, but its structure makes future changes more difficult, more risky, or more time-consuming. The term was popularized by Martin Fowler and Kent Beck and refers to patterns like excessively long methods, duplicated code, and overly coupled classes. Recognizing smells is a skill that improves with experience and is accelerated by automated analysis tools.
What are the most common code smells?
The most frequently encountered code smells are Long Method, Large Class (God Class), Duplicate Code, Feature Envy, and Primitive Obsession. Long Method and Duplicate Code are the two smells that developers encounter most often in practice, because they accumulate naturally as features are added without restructuring. Code review discipline and automated analysis tools help catch these early before they compound into larger structural problems.
How do you detect code smells automatically?
Static analysis tools like SonarQube, PMD, and ESLint detect many code smells using rule-based checks (method length, class size, cyclomatic complexity). More advanced detection uses AI-powered codebase intelligence tools that analyze call graphs, dependency chains, and change history to identify structural problems like Feature Envy, Shotgun Surgery, and Dead Code. These tools provide context that rule-based checkers miss, distinguishing between a legitimately complex method and one that should be decomposed.
Is all code smell bad?
Not all code smells require immediate action. Context matters. A long method in a mathematical algorithm may be clearer as a single unit than decomposed into tiny pieces. Speculative Generality is only a smell if the generality is never used. A small amount of duplication may be preferable to a premature abstraction that couples unrelated modules. The value of smell awareness is informed decision-making: you see the smell, understand the tradeoff, and choose whether to fix it now, defer it, or accept it.