Skip to main content
CleanCodeMastery

Feature Envy: The Method That Sits in Someone Else's Class All Day

Learn the Feature Envy code smell with a simple school story. When a method keeps using another class's data more than its own, it probably belongs in that other class. Cure it with Move Method.

26 min read Updated June 11, 2026beginner
code-smellscouplersfeature-envymove-methodencapsulationtypescriptcsharp

🎒 The boy who spent all day in the neighbour's class

Let me tell you about Ravi from Class 7-B.

Ravi has his own classroom. His own desk, his own bench, his own blackboard, his own class teacher — Meena ma'am. But here is the strange thing: Ravi is never there. Every morning he keeps his bag in 7-B, signs the 7-B attendance register, and immediately walks next door to Class 7-A.

Why? Because all his work needs things from 7-A. The attendance register he updates belongs to 7-A. The charts he draws use 7-A's marks. The notice he writes is about 7-A's picnic. The whole day he stands at 7-A's shelves, opening their cupboard, borrowing their chalk, reading their register. By lunch, the 7-A students know exactly where Ravi will be standing — at their cupboard, with their register open.

It started small, mind you. On the first day Ravi only borrowed one chart. The next week he needed the marks list too. By the end of the month he had memorised where 7-A kept everything — third shelf for registers, left drawer for chalk, the picnic file behind the dictionary. He knew the neighbour's classroom better than his own.

Now watch the trouble grow, person by person.

Sunita ma'am, the 7-A class teacher, wants to rearrange her shelves before inspection. She cannot! If she moves the registers to the second shelf, Ravi's entire routine collapses, and the picnic notice will be late. Her own classroom, and she needs an outsider's permission to move a shelf.

The 7-A students grumble: "Who is this boy? Why is he always opening our cupboard? Now we have to keep everything unlocked just for him." The cupboard that used to be locked stays open all day — because one boy from another class needs it.

And in 7-B, Meena ma'am stares at her attendance register: "Ravi is on my list, but I never see him. What does he even do? If I plan a class activity, can I count on him?" She cannot. Ravi belongs to her on paper only.

One day the headmistress, Mrs. Iyer, watches all this from the corridor. She does not scold anyone. She just says one very simple sentence: "If all of Ravi's work uses 7-A's things, then Ravi belongs in 7-A. Transfer him."

One transfer order. The cupboard gets locked again, Sunita ma'am rearranges her shelves freely, and Meena ma'am's register finally tells the truth.

That is the whole lesson of today's smell. In code, we often find a method that is exactly like Ravi. It is written inside class A, but every line of it uses the data of class B. It calls B's getters again and again. It hardly touches its own class. Such a method is envious of the other class — and the fix is usually the same as Mrs. Iyer's order: transfer it.

Figure 1: Ravi's school day — almost every step happens in the wrong classroom

Look at the satisfaction scores in that journey. Everyone is unhappy in the middle — the borrower, the lender, the teachers. The only happy moment is the transfer at the end. Keep that picture in mind; the rest of this post is just that journey written in TypeScript and C#.

🔍 What is this smell?

Feature Envy is a code smell where a method is more interested in another class's data than in the data of its own class. The "features" it envies are the fields and getters of someone else's object.

Martin Fowler describes it in his book Refactoring as a method that seems to spend more time talking to another object than to its own. The classic giveaway is a method that invokes half-a-dozen getter methods on another object to calculate some value.

Why is this a smell and not just a style choice? Because of one golden rule of object-oriented design:

Data and the behaviour that uses that data should live together, in the same class.

When they live together, a change happens in one place. When they drift apart, a change in the data class breaks code sitting in faraway files. Feature Envy is the early-warning bell that data and behaviour have drifted apart.

💡

A quick mental test: read the method and ask, "If I deleted every line that touches the other object, what is left?" If almost nothing is left, the method does not really belong here. It belongs with the object it keeps touching. Fowler's advice is simple: a method should be in the class whose data it uses most.

One more important point. Feature Envy belongs to a family of smells called the Couplers. Coupling means how tightly two classes are tied to each other. The Couplers — Feature Envy, Inappropriate Intimacy, Message Chains, and Middle Man — are all about classes that are tied too tightly, or tied in the wrong way. Feature Envy is the first and most common one: a one-way pull, where one method keeps reaching into another class.

There is also a famous one-line principle hiding behind this smell: "Tell, don't ask." Healthy code tells an object what to do — student.grade() — and lets the object use its own data. Envious code asks the object for raw data — marks, max marks, attendance — and then does the thinking outside. Every time you catch yourself asking-asking-asking, suspect that the thinking should move inside.

College corner: researchers have turned this smell into a measurable detection strategy. Lanza and Marinescu, in Object-Oriented Metrics in Practice, flag a method as envious when three metrics line up: ATFD (Access To Foreign Data — how many fields/getters of other classes the method touches) is high, LAA (Locality of Attribute Accesses — the fraction of touched data that belongs to the method's own class) is low (below about one-third), and FDP (Foreign Data Providers — how many distinct classes provide that foreign data) is small, often just one. That last condition matters: ATFD high + FDP equal to one means the method's heart belongs to exactly one other class — a perfect Move Method candidate. Tools like JDeodorant and code-quality linters implement variations of exactly this formula. Notice how the formal metrics are just Mrs. Iyer's observation in mathematical clothes: lots of foreign touches, almost no local touches, all pointing at one neighbour.

Here is the whole topic in one map before we go deep:

Figure 2: Feature Envy at a glance — signs, costs, cures, and the cases you leave alone

👀 How to spot it

Here is a simple checklist. Read any method and tick the boxes:

  • The method calls getters on one foreign object many times — order.lines, order.taxRate, order.currency — like a hand going into someone else's tiffin again and again.
  • The method uses its own class's fields zero or one time.
  • The method takes an object as a parameter and then pulls it apart, field by field, to compute something.
  • If you imagine moving the method into the other class, it would suddenly read more naturally — order.total() instead of printer.computeTotalOf(order).
  • The envied class has many getters that exist only so outsiders can pull data out and compute elsewhere.
  • Inside one long method, one block mentions a single collaborator far more than anything else. That block is the envious part, even if the rest of the method is fine.

A small comparison table makes the difference clear:

Question to askHealthy methodEnvious method
Whose data does it read most?Its own class's fieldsAnother object's fields
How many getter calls on one foreign object?Zero to twoThree, four, five or more
Where would the logic read most naturally?Right where it isInside the other class
What happens if the other class changes shape?NothingThis method breaks
Does the other class need public getters just for this?NoYes — getters multiply

Notice the counting trick. One getter call on another object is normal collaboration — friends do talk. The smell starts when the count and the pattern show that the method's heart is elsewhere. If you actually count the data touches of a typical envious method, the picture looks like this:

Figure 3: Count the touches in an envious method — the slices say everything

When the biggest slice belongs to someone else's class, the method is Ravi. You do not even need a tool — just read the method once with a pencil and make two tally marks: foreign and own.

⚠️ Why it is a problem

Let us count the real costs, just like Mrs. Iyer counted Ravi's hours.

1. It breaks encapsulation. Encapsulation means a class keeps its inner details private and can change them freely. But the envious method depends on the exact inner shape of the other class. If Order changes how it stores tax, a method in some distant InvoicePrinter file breaks. The person editing Order may not even know that file exists. This is Sunita ma'am unable to move her own shelves.

2. It scatters one idea across many files. Suppose five different classes all compute things from Order's data. Now the "rules about orders" live in five places. To understand what can happen to an order, a new programmer must hunt through the whole project instead of opening one file.

3. It forces wide, leaky interfaces. To feed the envious methods, Order must expose a getter for every field. Private things become public. Once public, they can never be safely changed, because who knows who is reading them. This is the cupboard that must stay unlocked all day for one outsider.

4. It invites duplication. Once the data is exposed, every client computes its own version of "the total". Three slightly different total formulas appear in three files. One has a bug. Good luck finding it.

Here is the picture. One method, sitting in the wrong class, with all its arrows reaching across the boundary:

Figure 4: The envious method's arrows all point into the other class — like Ravi standing in 7-B but doing all his work in 7-A

Count the arrows. Four arrows go out, zero stay home. That picture is Feature Envy. You do not even need to read the code — the arrows tell the story.

And duplication does not stay still. Watch what happens to one envious formula over a few months of normal development, as more services need the same answer:

Figure 5: Copies of the same envied formula multiply over time — each copy is a future bug

Month 1: the printer computes the grade. Month 3: the promotion checker copies the formula. Month 6: the SMS service and the dashboard copy it. Month 9: seven copies, and the school changes the grading rule. Someone now plays a treasure hunt across seven files — and usually finds six.

College corner: in coupling terms, every envious method raises the CBO (Coupling Between Objects) of its class and raises the efferent coupling (outgoing dependencies) of its module, while lowering the cohesion of both classes involved — the envied class's data is used mostly from outside (high LCOM, Lack of Cohesion of Methods), and the envious class hosts logic unrelated to its own fields. Structured maintenance studies consistently find that classes with high ATFD-style envy scores are changed together with their envied classes far more often than average — the smell literally shows up in version-control co-change history. If two files always appear in the same commits, check whether one of them is envious of the other.

⚠️

Feature Envy rarely stays alone. If you leave it, it grows. The envied class exposes more getters, more outsiders start computing with them, and soon two classes are reaching into each other both ways. That mutual version has its own name — Inappropriate Intimacy — and it is much harder to untangle. Cure the envy while it is still one-directional.

💻 A real-life code example

Let us put Ravi's story into code. Our school app prints report cards. Someone wrote a ReportCardPrinter, and inside it, a method that computes a student's grade:

class Student {
  constructor(
    public name: string,
    public marks: number[],       // marks in each subject
    public maxMarksPerSubject: number,
    public attendancePercent: number,
  ) {}
}
 
class ReportCardPrinter {
  printGradeLine(student: Student): string {
    // Every single line below reads Student's data.
    const total = student.marks.reduce((sum, m) => sum + m, 0);
    const maxTotal = student.marks.length * student.maxMarksPerSubject;
    const percent = (total / maxTotal) * 100;
 
    let grade = "C";
    if (percent >= 90) grade = "A+";
    else if (percent >= 75) grade = "A";
    else if (percent >= 60) grade = "B";
 
    // Even the attendance rule reads Student's data!
    if (student.attendancePercent < 75) grade = grade + " (attendance short)";
 
    return `${student.name}: ${percent.toFixed(1)}% — Grade ${grade}`;
  }
}

Read printGradeLine slowly. How many times does it touch student? Name, marks (twice), max marks, attendance — six touches. How many times does it touch ReportCardPrinter's own fields? Zero. There are no own fields to touch!

Watch the same method as a conversation. It looks less like two classes talking and more like an interrogation:

Figure 6: The printer interrogates the student — five questions for raw data, then computes outside

This method is Ravi. It sits in the printer class but does all its work with the student's things. And the damage is already starting:

  • The grading rule (90 is A+, 75 is A...) is a rule about students, but it lives in a printing class.
  • If tomorrow a PromotionChecker class also needs grades, it will copy this formula. Two copies. Then a ParentSmsService copies it again. Three copies.
  • If the school changes the rule (say A+ becomes 95), someone must find and fix every copy.
  • Student must keep all its fields public forever, because unknown outsiders are reading them.

The same disease appears in every language. Here it is in Python, in three short lines — a "helper" function pulling a parcel apart:

# Smelly: a free function interrogating someone else's object
def delivery_charge(parcel):
    charge = parcel.weight_kg * parcel.rate_per_kg      # foreign touch 1, 2
    if parcel.distance_km > 500:                         # foreign touch 3
        charge *= 1.5
    if parcel.is_fragile:                                # foreign touch 4
        charge += 50
    return charge
 
# Healthy: the parcel computes its own charge
class Parcel:
    def delivery_charge(self):
        charge = self._weight_kg * self._rate_per_kg
        if self._distance_km > 500:
            charge *= 1.5
        if self._is_fragile:
            charge += 50
        return charge

Four foreign touches, zero own touches — in Python the smell is even easier to write because nothing stops a function from reading any attribute. The discipline must come from you.

🛠️ Cleaning it up, step by step

The cure follows the headmistress's order: transfer the boy to the class where his work is. In refactoring language, that is Move Method.

Step 1 — Find the envious part. Sometimes a whole method is envious, like ours. Sometimes only a block inside a long method is envious. In that case, first use Extract Method to pull the envious block into its own small method. Now you have a clean piece to move.

Step 2 — Move the calculation to where the data lives. The grade is computed entirely from Student's data. So the method belongs on Student:

class Student {
  constructor(
    public name: string,
    private marks: number[],            // can be private now!
    private maxMarksPerSubject: number,
    private attendancePercent: number,
  ) {}
 
  percent(): number {
    const total = this.marks.reduce((sum, m) => sum + m, 0);
    const maxTotal = this.marks.length * this.maxMarksPerSubject;
    return (total / maxTotal) * 100;
  }
 
  grade(): string {
    const p = this.percent();
    let grade = "C";
    if (p >= 90) grade = "A+";
    else if (p >= 75) grade = "A";
    else if (p >= 60) grade = "B";
    if (this.attendancePercent < 75) grade += " (attendance short)";
    return grade;
  }
}

Step 3 — Let the old class simply ask. The printer now does only printing — its real job:

class ReportCardPrinter {
  printGradeLine(student: Student): string {
    return `${student.name}: ${student.percent().toFixed(1)}% — Grade ${student.grade()}`;
  }
}

Step 4 — Tighten the envied class. Look at the beautiful side effect: marks, maxMarksPerSubject, and attendancePercent became private. Nobody outside needs them anymore. The class's secrets are secret again. This is the real prize of the refactoring — not just tidier code, but restored encapsulation. The cupboard is locked again.

Step 5 — Check for stranded fields too. Sometimes the problem is reversed: a field sits in the wrong class and is used mostly by another class's methods. Then the cure is Move Field — shift the field so data and methods sit together. And if you discover that the moved methods and fields form a brand new idea (say, a GradingPolicy), pull them into their own class with Extract Class.

The whole refactoring, drawn as the states the code passes through:

Figure 7: The cure as a state machine — from envious method to locked cupboard

And the finished structure, as a class diagram. One public question goes in; all the detailed data access stays inside the class that owns the data:

Figure 8: After Move Method — the printer holds a reference and asks one question; the data is private
Figure 9: The arrows after the transfer — only one polite question crosses the boundary

Compare Figure 9 with Figure 4. Earlier, four arrows crossed the boundary. Now only one arrow crosses, and it carries a question ("what is your grade?"), not a raid on private data. All the detailed arrows stay inside Student, where they are safe.

🧪 The same smell in C#

The smell looks identical in C#. Here is a delivery app where BillingService envies Parcel:

// Smelly: BillingService computes everything from Parcel's data
public class BillingService
{
    public decimal DeliveryCharge(Parcel parcel)
    {
        decimal charge = parcel.WeightKg * parcel.RatePerKg;
        if (parcel.DistanceKm > 500) charge *= 1.5m;
        if (parcel.IsFragile) charge += 50m;
        return charge;
    }
}

Three reads of parcel, zero reads of this. Transfer the work:

public class Parcel
{
    private readonly decimal _weightKg;
    private readonly decimal _ratePerKg;
    private readonly int _distanceKm;
    private readonly bool _isFragile;
 
    public decimal DeliveryCharge()
    {
        decimal charge = _weightKg * _ratePerKg;
        if (_distanceKm > 500) charge *= 1.5m;
        if (_isFragile) charge += 50m;
        return charge;
    }
}
 
public class BillingService
{
    public decimal DeliveryCharge(Parcel parcel) => parcel.DeliveryCharge();
}

The fields turned private readonly. The pricing rule has exactly one home. Any other service that needs the charge asks the parcel — no copies, no envy.

🏢 Where this smell hides in real projects

Feature Envy is one of the most common smells in real codebases. Research it in any project and you will find it wearing these disguises:

1. The anemic domain model. Many projects have entity classes that are pure bags of properties — a Data Class with getters, setters, and nothing else. Then "service" classes hold all the logic: OrderService.calculateTotal(order), OrderService.canCancel(order). Every one of those service methods is envious by force, because the entity refuses to do anything itself. The smell here is not one method — it is the whole architecture, and the cure is to gradually push behaviour into the entities.

2. Report and export builders. Classes named ReportBuilder, PdfExporter, CsvWriter often start innocently, then slowly absorb business calculations. They pull weight, distance, and zone out of a Shipment to compute a cost that Shipment should compute itself. Keep the formatting in the builder; move the calculating to the data's home.

3. Helper and util classes. OrderUtils.getDisplayName(order), DateHelper.financialYearOf(invoice). A static helper that takes an object and interrogates it is envy in its purest form — it cannot even pretend to have its own data.

4. After a half-finished refactoring. Someone moved fields from class A to class B but forgot to move the methods that used them. The stranded methods now reach across the new boundary. The envy here is an arrow pointing at unfinished work.

5. Controllers and handlers. Web controllers that take a request DTO and run business rules on its fields, instead of handing it to a domain object. The controller should translate and delegate, not calculate.

🤔 When it is okay to ignore

A good doctor does not operate on every spot. Some "envy" is healthy and deliberate. Be honest about these cases:

SituationEnvy or not?Why
Strategy object computing from a context's dataNot a smellThe whole point of Strategy is to keep swappable logic outside the data. Moving it back destroys the pattern.
Visitor walking a structure and computing on each nodeNot a smellVisitor exists precisely to add operations without editing the node classes.
A serializer or UI formatter reading many fieldsUsually finePulling JSON or screen-layout logic into a domain class couples it to a file format or UI. That cure is worse than the disease.
A method that envies two objects equallyJudgement callYou cannot move it to both. Put it with the object whose data dominates, or extract a new class that owns the rule.
One or two getter calls on a collaboratorNormalObjects must talk. Envy is about a pattern of heavy one-sided use, not a single polite question.
A method reading 5+ fields of one foreign object to compute a valueReal envy — fix itThis is the textbook case. Move Method pays off immediately.

A simple way to place any method you are unsure about: plot how much it uses its own data against how much it uses foreign data.

Figure 10: The placement chart — methods in the bottom-right corner are transfer candidates

Read the chart like a teacher reads a seating plan. Top-left is home territory — the method uses its own class heavily, leave it. Bottom-right is the Move Method zone — heavy foreign use, almost no own use. Top-right (heavy both ways) usually means the method is doing two jobs and should be split with Extract Method first. Bottom-left is a trivial method that touches almost nothing — harmless.

College corner: the "envies two objects equally" row deserves a formal note. When a computation needs equal data from classes A and B, neither Move Method direction wins; the standard answer is to recognise a missing abstraction — a third class whose responsibility is exactly that relationship (for example, a FinePolicy between Book and Member, or an ExchangeRate between two Currency objects). This is Extract Class guided by the Single Responsibility Principle: the rule that varies independently gets its own home. In Domain-Driven Design language, such classes are often domain services — stateless objects that hold behaviour belonging to no single entity. Knowing this saves you from ping-ponging a method between two classes forever.

ℹ️

A useful habit when you are unsure: imagine the change that is most likely to happen next. If the envied class changes its fields, which files break? If the answer is "files far away, owned by other people", the envy is dangerous — fix it. If the answer is "only this one nearby file, and both change together anyway", you may leave it for now.

💊 Which refactorings cure it

RefactoringWhen to use itWhat it does
Move MethodThe whole method is enviousTransfers the method into the class whose data it uses most — the primary cure
Extract MethodOnly a block inside a method is enviousIsolates the envious block first, so it can be moved cleanly
Move FieldA field is used more by another class than its ownMoves the data to where the behaviour is, when that direction makes more sense
Extract ClassThe envious code reveals a missing conceptCreates a new class that owns both the data and the rule

The order matters in practice: Extract Method first, then Move Method is the everyday combo. Extract isolates the envy; Move sends it home.

📦 Quick revision box

+================================================================+
|                  FEATURE ENVY — QUICK REVISION                  |
+================================================================+
| STORY    : Ravi sits in 7-B but does all his work in 7-A.       |
|            Transfer him to 7-A!                                 |
|                                                                 |
| SMELL    : A method uses ANOTHER class's data far more          |
|            than its own class's data.                           |
|                                                                 |
| SPOT IT  : Count the touches —                                  |
|            foreign getters: many | own fields: ~zero            |
|                                                                 |
| COSTS    : breaks encapsulation, scatters rules,                |
|            forces public getters, invites duplicate formulas    |
|                                                                 |
| CURE     : Extract Method (isolate the envious part)            |
|            -> Move Method (send it to the data's home)          |
|            -> make the envied fields private again              |
|                                                                 |
| IGNORE   : Strategy / Visitor (envy on purpose),                |
|            serializers & UI formatters, two-object ties         |
|                                                                 |
| RULE     : Data and the behaviour that uses it                  |
|            should live in the SAME class.                       |
+================================================================+

✏️ Practice exercise

Time to be the headmistress. Here is a small library system:

class Book {
  constructor(
    public title: string,
    public dailyFine: number,
    public dueDate: Date,
    public isReferenceOnly: boolean,
  ) {}
}
 
class LibraryDesk {
  fineFor(book: Book, returnedOn: Date): number {
    if (book.isReferenceOnly) return 0;
    const msLate = returnedOn.getTime() - book.dueDate.getTime();
    const daysLate = Math.max(0, Math.ceil(msLate / 86_400_000));
    let fine = daysLate * book.dailyFine;
    if (daysLate > 30) fine += 100; // long-overdue penalty
    return fine;
  }
 
  receiptLine(book: Book, returnedOn: Date): string {
    return `${book.title} — fine: Rs. ${this.fineFor(book, returnedOn)}`;
  }
}

Your tasks:

  1. Count the touches. In fineFor, how many times is book read? How many times are LibraryDesk's own fields read? Write the two numbers and say whether this is Feature Envy.
  2. Apply Move Method. Move the fine calculation onto Book as fineIfReturnedOn(returnedOn: Date): number. Which of Book's fields can now become private?
  3. Check the leftover. After the move, look at receiptLine. Is it envious too, or is it doing the desk's honest job (formatting)? Explain in one sentence.
  4. Place it on the chart. Using Figure 10's axes, plot fineFor (before), receiptLine, and your new fineIfReturnedOn. Which quadrant does each land in?
  5. Think ahead. A new SmsReminderService must warn members about expected fines. With your refactored code, how many lines does it need to get the fine? How many would it have needed with the old code?
  6. Bonus. Suppose the school library and the city library compute fines differently. Would you keep fineIfReturnedOn on Book, or move the rule into a separate FinePolicy class using Extract Class? When does the second choice become better?

If you can answer question 6 with "when the rule varies independently of the data", you have understood not just the smell, but design itself. Well done!

Frequently asked questions

What is the Feature Envy code smell in simple words?
Feature Envy is when a method living in one class keeps using the data of another class — calling its getters again and again — while hardly touching its own class's data. The method is 'envious' of the other class. It usually means the method is sitting in the wrong class and should move to where the data lives.
How do I detect Feature Envy in my code?
Count the touches. If a method reads fields or calls getters of another object three, four, five times but uses its own class's fields zero or one time, that method is envious. Another sign: the method receives an object as a parameter and then pulls it apart field by field to do a calculation.
Which refactoring fixes Feature Envy?
Move Method is the main cure: shift the envious method into the class whose data it loves. If only a part of the method is envious, first use Extract Method to separate that part, then move it. If a field is the thing in the wrong place, use Move Field.
Is Feature Envy always bad?
No. Patterns like Strategy and Visitor deliberately keep behaviour outside the data classes — that is their whole purpose. Also, printing, saving, and serialisation code naturally reads another object's fields. Moving that logic into the domain object would couple it to UI or database details, which is worse.
What is the connection between Feature Envy and anemic data classes?
An anemic Data Class is a class with fields and getters but no behaviour. Because the class itself does nothing, every calculation about its data is forced to live somewhere else. Those outside methods all become envious. Curing the envy means giving the data class real methods, so data and behaviour live together.

Further reading

Related Lessons