IBM Dev Day: Bob in Action. Learn to build with purpose using IBM Bob 2.0 | Register now

Code smells, explained

Published 13 July 2026
Extreme Close up on angry man's face
By Dave Bergmann

“Code smell” is an informal term used by programmers to describe software design patterns that are common to bad code. Code smells are best understood not as bugs or defects unto themselves, but as warning signs of poor code quality.

The term “code smell” was coined by Kent Beck and popularized in the seminal 1999 book he co-wrote with Martin Fowler, Refactoring: Improving the Design of Existing Code—specifically, in the chapter titled “Bad Smells in Code.” Fowler himself succinctly articulates a code smell as “a surface indication that usually corresponds to a deeper problem in the system.”1  

Unlike a true software bug, a code smell does not prevent source code from compiling, running and performing its desired function—nor does its presence always indicate an actual problem. Imagine entering a home and smelling something funky or moldy. That smell might be from expired food in your refrigerator or garbage that needs to be taken outside; more seriously, it could be from mold or something rotting in your walls—but it could also just be from some seriously pungent cheese that’s perfectly safe to eat. Regardless, the smell warrants further inspection.

For an experienced software engineer, certain patterns in code simply don’t “smell” right. Over time, one might instinctually associate their presence with certain complications, inefficiencies or future problems. Naming and popularizing specific code smells facilitates familiarity with these counterproductive patterns (or “anti-patterns”) and the design principles they violate. That familiarity, in turn, makes these anti-patterns more likely to be noticed and remedied during code review. As Fowler puts it, “a smell is by definition something that’s quick to spot—or sniffable.”

Code smells, if left unaddressed, are major contributors to technical debt. As one study asserts, 75% of code review defects don’t affect program execution but do “impact the evolvability of the software.”2 Even if a certain anti-pattern or bad habit doesn’t cause a bug today, it significantly increases the risk of bugs, crashes or security vulnerabilities in your codebase emerging later. At a minimum, the presence of known code smells reduces your code’s readability and maintainability, making it harder to understand, update and improve.

Types of code smells

Though many common code smells continue to be known by the names coined by Beck and Fowler in Refactoring, there exists no single canonical, universally accepted list of code smells. But whether a given code smell should be referred to by one name or another—or be categorized as its own anti-pattern rather than a subtype of another—is largely immaterial. What matters is whether a code smell’s name and description are clear and intuitive enough to help software development teams share and implement productive design principles.

There is, likewise, no universally agreed-upon taxonomy for different categories of code smells. This article primarily draws upon the taxonomy proposed by Jerzyk and Madeyski in 2023.3 Their findings were published in the helpful catalog maintained at codesmells.org, which provides additional context, code examples and suitable refactoring techniques for each.

Jerzyk and Madeyski note that the most commonly referenced taxonomy of code smells follows the five groupings proposed by Mäntylä and Lassenius in 2006, who categorized the 22 code smells introduced by Fowler and Beck (and one more of their own) into five distinct groupings: Bloaters, Object-Orientation Abusers, Change Preventers, Dispensables and Couplers.4 After their own attempt at an exhaustive review of both formally published literature and “grey material” (such as blogs, forums and wikis), Jerzyk and Madeyski catalogued 56 different code smells into 9 groupings (which include the 5 named earlier in this paragraph).

While this section mostly follows the 9-category structure they proposed, it’s important to note that such groupings are informal and subjective: the most useful taxonomy is whichever you find most intuitive. Understanding the different kinds of complications or inefficiencies that code smells might introduce can provide a more meaningful conception of code smells than rote memorization of individual smells. As Mäntylä articulates, “with a long flat list of code smells it is easy to lose the overall picture.”5 

Bloaters

Bloaters are code smells that often result in methods, classes or code blocks growing so large that they become unwieldy. This decreases readability and makes code harder to maintain or modify.

Examples of bloaters include:

  • Data clumps: Groups of variables that frequently appear together everywhere.

  • Large class: A class trying to do too much and containing too many variables, lacking cohesion.

  • Long function (long method): A method containing too many lines of code.

  • Long parameter list: Functions requiring too many arguments to operate correctly.

  • Primitive obsession: Using primitive data types instead of specialized small objects.

Change preventers

Change preventers, as implied by their name, are code smells that hinder the ability to modify, add to or otherwise further develop software. A typical instance of a change preventer is a code structure that forces you to make a series of edits in multiple places just to implement a single, simple modification.

These kinds of code smells violate Robert C. Martin’s single responsibility principle (SRP), which asserts that changes to any one code module should only be able to originate from one place. As Martin suggests in a blog post about SRP, “Gather together the things that change for the same reasons. Separate those things that change for different reasons.”6

Examples of change preventers include:

  • Shotgun surgery: Implementing a single change requires modifications across many scattered modules simultaneously (which is essentially the opposite of large class).

  • Divergent change: Like the inverse of shotgun surgery, implementing a single change requires many modifications within a single class.

  • Callback hell: Code structure in which many methods are nested deep within one another across many indented tabs and curly brackets, obscuring cause and effect and making code challenging to read and maintain.

Couplers

Couplers are code smells that violate the core design principle of low coupling by creating excessive dependencies between different classes. This reduces code reusability and complicates (or even prevents) independent unit testing.

Examples of couplers include:

  • Feature envy: A method accesses the data of another object excessively.

  • Insider trading (aka inappropriate intimacy): One class uses the internal fields or methods of another class.

  • Message chains: A long sequence of chained method calls between objects.

Data dealers

Data dealers pass data through far more classes or functions than are necessary. This creates unnecessary dependencies and complexity, often resulting in code elements that merely hold or distribute data without providing any meaningful behavior.

Examples of data dealers include:

  • Middle man: A class whose only job is delegating to others.

  • Tramp data: When data “hitchhikes” through a chain of methods that make no use of it.

  • Global data: Structure in which variables can be modified from anywhere in the codebase, making every function in the codebase a suspect when something breaks.

Dispensables

Dispensables are, intuitively, dispensable code elements: their removal would make the codebase cleaner and easier to read without any meaningful impact on overall functionality.

Examples of dispensables include:

  • Comments: While comments are generally a good thing, they’re sometimes used as a “deodorant” for code smells, explaining rather than improving. For instance, if a comment describes what is happening in a particular section of code, it’s being used to mask code that is not intuitive or readable enough by itself. Individual comments can also become redundant or outdated over time. More specific comment behaviors appear in other code smells.

  • Data class: Classes containing only fields and accessors, lacking meaningful behavior.

  • Dead code: Code elements that are no longer executed because, over time, refactoring and other modifications have made them obsolete.

  • Duplicate code: Identical or very similar code structures in multiple places.

  • Lazy class (aka lazy elements): Classes or functions that do too little to exist.

  • Speculative generality: Unneeded code added to support hypothetical future features.

Functional abusers

Functional abusers are code smells that eschew object-oriented programming principles by forcing functional patterns into an object-oriented codebase.

Examples of functional abusers include:

  • Loops: Using traditional loops instead of modern pipeline operations. Though Fowler considered almost all loops to be outdated, Jerzyk posited that imperative loops, in particular, are the primary problem.

  • Mutable data: Variables whose state changes unexpectedly, causing unpredictable side effects.

  • Side effects (aka impure functions): Methods that do more than their name and primary purpose would imply.

Lexical abusers

Lexical abusers are code smells originating from poor naming conventions, inconsistent formatting or cryptic syntax. Simply put, they’re instances in which the wording of code doesn’t intuitively match the corresponding code behavior, hindering readability.

 Examples of lexical abusers include:

  • Magic number: Unnamed numbers inserted in code without adequate explanation or context.

  • Fallacious comment: Comments that are no longer accurate because the code around them has changed. Because comments aren’t actually executed, they often escape linters and other automated checks.

  • Mysterious name: Functions or variables named poorly, hiding their actual intent. 

  • Fallacious method name: Functions whose names are actively misleading, based on common conventions and expectations. For instance, a function called getSomething that doesn’t actually return anything.

Obfuscators

Obfuscators are code elements written in an unnecessarily convoluted, complex or “clever” way that buries the code’s underlying intent behind unnecessary abstraction. These code smells make source code confusing to read and therefore make it difficult for programmers in the future to understand and modify it as needed.

Examples of obfuscators include:

  • Vertical separation: Unnecessarily large distances between related, relevant code elements, such as a variable declared at the top of a method that isn’t used until 50 lines later.

  • Obscured intent: The broader category of functions, variables, names and numbers whose purpose is neither intuitive nor made clear in context.

  • Complicated Boolean expression: Convoluted logical flows, such as those containing double negatives or complex chains of  IF and  IF NOT and  AND and  AND NOT .

  • Clever code: Code that works, but substitutes hard-to-follow bespoke language in situations for which there already exist widely understood built-ins and other conventional solutions.

Object-oriented abusers

Object-oriented abusers (or object-orientation abusers) are code smells that fail to fully or correctly apply object-oriented design principles. For instance, switch statements are useful in procedural programming, but should be avoided in object-oriented programming.

Examples of object-oriented abusers include:

  • Alternative class with different interfaces: Classes performing similar functions using completely different method names.

  • Refused bequest: Subclasses that do not need or use inherited methods.

  • Switch statements (aka Conditional Complexity aka Repeated Switches): The exact same switch statement duplicated across the codebase.

  • Temporary field: A variable created where it often isn’t needed, typically used only in select circumstances.

Refactoring code smells

The primary benefit of understanding code smells is that recognizing them allows for more effective refactoring: the practice of updating source code without modifying its external behavior or functionality. Routinely tidying up code through refactoring is essential for reducing technical debt and facilitating fast, effective improvements and additions to your codebase over time.

Refactoring can largely be understood as the process of identifying and addressing code smells. Its goal is to fix problems before they negatively impact functionality—at which point the process is more like debugging than refactoring.

Modern agentic engineering platforms, such as IBM Bob, often provide automated refactoring suggestions in real time. Through extensive training on common code smells in the context of actual codebases—rather than rote memorization of code smell definitions—such platforms’ AI code refactoring capabilities enable developers to scale up their output without a corresponding increase in unwieldy AI-generated code that will be difficult to understand and modify later on.

Author

Dave Bergmann

Senior Staff Writer, AI Models

IBM Think

Related solutions
IBM Bob

Accelerate software delivery with IBM Bob™, your AI partner for secure, intent-aware development.

Explore IBM Bob
AI for developers solutions

Develop, deploy and manage AI applications faster with enterprise-ready tools.

Explore AI for developers
Application modernization services

Reimagine legacy systems with intelligent AI modernization.

Explore application modernization services
Take the next step

Harness generative AI and advanced automation to deliver enterprise‑ready code with greater speed and consistency. Bob™ models augment developer skill sets, streamlining modernization workflows and simplifying complex development tasks.

  1. Discover AI coding agent
  2. Explore AI for developers solutions