Inappropriate Intimacy: Two Classes That Walk Into Each Other's Kitchens
Learn the Inappropriate Intimacy code smell with a story of two neighbours who rearrange each other's kitchens. When two classes poke each other's private parts, neither can change alone. Learn the Law of Demeter and the refactorings that restore privacy.
🏠 The two neighbours who shared everything — including their kitchens
In Shanti Nagar colony, Mrs. Sharma and Mrs. Verma live side by side, house number 14 and house number 15. They started as good neighbours. A cup of sugar here, a bowl of dal there. Festival sweets exchanged over the wall. Very nice.
But slowly, things went further. Mrs. Sharma stopped knocking. She walks straight into Mrs. Verma's kitchen, opens the masala cupboard, and takes what she needs. Mrs. Verma does the same next door. Then it got worse. One Tuesday, Mrs. Sharma rearranged Mrs. Verma's shelves — "the haldi should be on the left, na? Easier for me when I come." Mrs. Verma, not to be outdone, re-labelled all of Mrs. Sharma's dabbas in her own handwriting. By winter, each one kept a spare key to the other's house on her own keyring, next to her own.
For a while it even felt convenient. No knocking, no asking, no waiting. That is the dangerous thing about this arrangement — it feels efficient right up until someone wants to change something.
Then came March. Mrs. Verma's son got a good job, and she decided to renovate her kitchen. New cupboards, modular shelves, everything in new places. The carpenter came, measured, gave a quote. And then Mrs. Verma stopped him at the door.
She cannot renovate. Mrs. Sharma's daily cooking depends on the old arrangement. If the haldi dabba moves from the left shelf, Mrs. Sharma's lunch fails — and Mrs. Sharma will not even know why until she is standing in front of a rearranged cupboard with a hot tawa waiting at home. And the dependency runs the other way too: Mrs. Sharma cannot lock her house and visit her village for a month, because Mrs. Verma's evening cooking needs her kitchen.
Mr. Verma summarised it at dinner: "We are two families on paper. But in reality? Neither house can change anything alone. Two kitchens, one tangled household. No privacy, total dependency."
This is exactly what happens to two classes in code when they become too close. Class A reads and writes class B's fields. B reaches back and pokes A's internals. Each keeps a reference to the other — a spare key. Soon you cannot modify, test, or reuse one without dragging the other along. This smell is called Inappropriate Intimacy.
🔍 What is this smell?
Inappropriate Intimacy is a code smell where two classes know far too much about each other's private parts. Instead of talking through a small, polite interface — like knocking on the door and asking — they directly access each other's fields, internal methods, and private state.
Martin Fowler lists it among the coupling smells in Refactoring: sometimes classes become far too intimate and spend too much time delving into each other's private parts. Refactoring Guru describes it the same way — one class uses the internal fields and methods of another class.
Notice how this relates to its little brother, Feature Envy:
- Feature Envy is one-way: a method in
Akeeps readingB's data. One nosy neighbour. - Inappropriate Intimacy is mutual and deeper:
AandBread and write each other, hold references to each other, and depend on each other's exact internal shape. Two tangled households.
A quick test: try to write a unit test for one class using a simple fake of the other. If you cannot — if the test needs the full, real partner class with all its internals — the two classes are intimate. Healthy collaborators can be tested with a stub; intimate ones come only as a pair.
College corner: classical structured-design theory (Stevens, Myers, and Constantine, 1974) ranked coupling from best to worst: data coupling (passing simple values), stamp coupling (passing structures), control coupling (passing flags that steer the callee), common coupling (sharing global data), and at the very bottom, content coupling — one module directly reading or modifying another module's internal data. Inappropriate Intimacy is content coupling wearing object-oriented clothes. When Transaction writes account.balance with its own hands, that is textbook content coupling — the worst rank on a fifty-year-old scale. The ranking survives because the reasoning survives: the lower the coupling rank, the smaller the surface through which change can propagate.
⚖️ The Law of Demeter: "talk only to your close friends"
To understand why this is wrong, we learn one famous rule. In 1987, researchers on the Demeter project (Karl Lieberherr and colleagues at Northeastern University) wrote down a style rule for object-oriented programs, published in their OOPSLA 1988 paper and a 1989 article. It became known as the Law of Demeter, with the friendly motto:
"Only talk to your immediate friends."
In simple words: a method may talk to —
- itself (its own fields and methods),
- its parameters (things handed to it),
- objects it creates itself,
- its direct component objects (its own fields).
These are its close friends. What it must not do is reach through a friend into the friend's internal things — the friend's friend is a stranger. You may ask your neighbour for sugar. You may not walk into her kitchen, open her cupboard, and rearrange her dabbas.
College corner: the formal statement is worth memorising for exams and interviews. For all classes C, and for all methods M attached to C, all objects to which M sends a message must be instances of: (1) the class of M's argument objects, including C itself; (2) the classes of C's instance variables; (3) objects created by M; or (4) global objects. The law is also called the Principle of Least Knowledge, and it has two variants: the object form (stated above, about runtime objects) and the class form (about declared types). A practical compile-time approximation that linters use: "no more than one dot per expression involving navigation", though that slogan is rougher than the real law — this.a.b is a violation while list.map(f).filter(g) is not, because the law is about whose internals you traverse, not about counting punctuation.
Inappropriate Intimacy is a head-on violation of this law, in both directions at once. Each class navigates into the other's private structure instead of asking for what it needs. (The one-directional, chained version of the violation is the Message Chains smell — read that post next; the law appears there too.)
👀 How to spot it
Run through this checklist for any pair of classes that always seem to appear together:
-
Areads or writesB's fields directly, andBdoes the same toA. - Bidirectional references:
Aholds aB, andBholds a back-reference to itsA, and both traverse freely. - One class exposes
internal/friend/ package-private members only so its partner can poke at them. - Every change to one class forces a change in the other. Their commits always come in pairs.
- A subclass leans so heavily on the parent's
protectedinternals that inheritance has become a back door, not a contract. - You cannot unit-test one class without constructing the full, real other class.
Here is a table to judge how close is too close:
| Behaviour between two classes | Verdict |
|---|---|
A calls a public method of B and uses the answer | Healthy — this is just talking |
A reads one getter of B occasionally | Fine — friends share a little |
A reads many of B's fields to compute things | Feature Envy — one-way, fix with Move Method |
A writes B's fields directly | Warning bell — B no longer controls its own state |
A writes B's fields and B writes A's fields | Inappropriate Intimacy — full smell |
A and B hold references to each other and mutate each other's collections | Severe intimacy — change one, break both |
| Subclass freely rummages in parent's protected state | Intimacy via inheritance — the "is-a" has become a back door |
The same table, drawn as a chart. Two questions decide everything: does the access write or only read, and does it go both ways or one way?
Bottom-left is normal life: polite, read-mostly, one-directional conversation. Move right (writes appear) and you lose control of your own state. Move up (both directions) and you lose the ability to change alone. The top-right corner is the two kitchens.
⚠️ Why it is a problem
1. The unit of change doubles. A class is supposed to be a unit you can change alone. When two classes share internals, every edit ripples across. You came to fix one file; you leave having edited two, and tested both, and prayed.
2. Encapsulation is dead. Fields that should be private are exposed for the partner. Once exposed, the class can never change its internal representation. Mrs. Verma can never renovate her kitchen.
3. Reuse is blocked. Want to use Transaction in a new reporting module? It comes chained to BankAccount, which comes chained to half the bank. You wanted one class; you got a wedding procession.
4. Testing becomes a pain. Neither class accepts a stub of the other, because each one depends on the partner's concrete internals, not on an interface. Tests become big, slow, and fragile.
5. Bugs hide in the shared state. When two classes both mutate the same fields, "who changed this value?" has two answers. Debugging means reading both classes at once, holding both in your head.
That fifth cost deserves its own picture. Ask a simple question about the smelly code we are about to read: who writes the account's balance?
In healthy code that pie has one slice. Every extra slice is one more suspect when the balance is wrong at 2 a.m. — and one more file you must read to understand a single field.
And the cost is not static; it grows with the level of intimacy. Watch how many files a single small change touches, as the relationship deepens:
Look at the arrow labels. It is not the number of arrows that frightens us most — it is the word writes. Reading a neighbour's nameplate is one thing; repainting her walls is another.
Bidirectional associations are the most common doorway to this smell. The moment Order knows its Customer AND Customer keeps a list of its Orders, both sides feel invited to manipulate the other's collections directly. If you only need one direction, keep only one. Every reference you do not hold is a temptation you do not face.
💻 A real-life code example
Let us write the two neighbours as code. A bank account and its transactions, tangled exactly like the two kitchens:
class BankAccount {
transactions: Transaction[] = []; // public — Transaction reaches in
balance = 0; // public — Transaction reaches in
apply(t: Transaction): void {
this.balance += t.amount; // reads Transaction's field
t.account = this; // WRITES Transaction's field
t.posted = true; // WRITES Transaction's field
this.transactions.push(t);
}
}
class Transaction {
amount = 0;
account: BankAccount | null = null; // spare key to the neighbour's house
posted = false;
reverse(): void {
if (!this.account) return;
this.account.balance -= this.amount; // WRITES account's field
const i = this.account.transactions.indexOf(this);
this.account.transactions.splice(i, 1); // mutates account's list!
}
}Read it like the colony story. BankAccount.apply walks into Transaction's house, flips its posted flag, and plants a back-reference — Mrs. Sharma relabelling the dabbas. Transaction.reverse walks into the account's house, changes the balance with its own hands, and removes itself from the account's own list — Mrs. Verma rearranging the shelves. Each class is cooking in the other's kitchen.
Played as a conversation, the smelly code sounds like this — notice there is not a single question in it, only trespassing:
What goes wrong in practice?
- Suppose the account should recompute interest whenever the balance changes. Where do you put that rule?
applychanges balance... but so doesreverse, from the other class. The rule has no single home. - Suppose balance must never go below zero. Who enforces it?
BankAccountcannot, becauseTransactioneditsbalancebehind its back. - Try to test
Transaction.reversealone. You cannot — it needs a realBankAccountwith a real list.
The same tangle, written in Python so you can see the smell has no favourite language:
# Smelly: each class mutates the other's state directly
class BankAccount:
def apply(self, t):
self.balance += t.amount
t.account = self # writes into Transaction
t.posted = True # writes into Transaction
self.transactions.append(t)
class Transaction:
def reverse(self):
self.account.balance -= self.amount # writes into account
self.account.transactions.remove(self) # mutates its listPython does not even have a private keyword to stop this — by convention an underscore prefix (_balance) says "do not touch", and the team's discipline is the only lock on the kitchen door. All the more reason to know this smell by heart.
🛠️ Cleaning it up, step by step
The headline of the cure: give each class one clear responsibility, and make all conversation go through a small, polite interface. We use several refactorings together.
Step 1 — Decide who owns what. The balance and the transaction history are the account's property. Only the account should ever change them. The amount is the transaction's property. This decision alone solves half the problem.
Step 2 — Move Method: bring the balance-changing logic home. The body of reverse() mutates account state, so that behaviour belongs on BankAccount. We move it there and give it an honest name:
class BankAccount {
private transactions: Transaction[] = []; // private again!
private balance = 0; // private again!
post(t: Transaction): void {
this.balance += t.amount();
this.transactions.push(t);
}
unpost(t: Transaction): void {
this.balance -= t.amount();
const i = this.transactions.indexOf(t);
if (i >= 0) this.transactions.splice(i, 1);
}
currentBalance(): number {
return this.balance; // a value handed over, not a field exposed
}
}Step 3 — Cut the back-reference (Change Bidirectional Association to Unidirectional). Does Transaction truly need to know its account? After step 2 — no. Whoever wants to reverse a transaction tells the account: account.unpost(t). The spare key is returned:
class Transaction {
constructor(private readonly _amount: number) {}
amount(): number {
return this._amount; // exposes a value, not its guts
}
}Step 4 — Make fields private and watch the compiler help you. Mark balance and transactions private. Every red error the compiler shows is one more place that was trespassing. Fix each by routing it through post, unpost, or currentBalance.
Step 5 — Where reaching-through remains, use Hide Delegate. If some caller was doing transaction.account.balance, give it a proper question to ask instead. Hiding navigation behind a method is exactly how you obey the Law of Demeter. (Careful — do not over-do it, or you will create a Middle Man. Balance, always balance.)
Step 6 — If a shared concept appears, use Extract Class. Sometimes two classes are intimate because a third class is hiding inside them — some idea they are both managing half of. For example, if account and transaction both fiddle with currency-conversion data, pull out a Money or CurrencyConverter class and let both depend on it cleanly. Two tangled neighbours often just need a proper shared community hall.
Step 7 — For parent–child intimacy, Replace Inheritance with Delegation. When a subclass is rummaging in its parent's protected fields, change the "is-a" to a "has-a". Now the relationship passes through a defined interface, like everyone else's.
The whole journey from tangle to clean, as states:
And the final structure as a class diagram — compare the small, clean surface here with the open kitchens of Figure 6:
Compare with Figure 6. Every arrow now goes one way, and every arrow carries a question ("what is your amount?") or a request ("please post this"). No arrow writes into another class's private field. Mrs. Verma can finally renovate: as long as amount() still answers correctly, Transaction can change its insides any way it likes — and so can BankAccount.
🧪 The same smell in C#
Here is the intimacy pattern in C#, with a Course and Student pair that mutate each other:
// Smelly: both classes reach into each other
public class Course
{
public List<Student> Enrolled = new();
public int Capacity = 30;
public void Add(Student s)
{
Enrolled.Add(s);
s.Courses.Add(this); // writes Student's collection
s.CreditsTaken += 4; // writes Student's field
}
}
public class Student
{
public List<Course> Courses = new();
public int CreditsTaken;
public void Drop(Course c)
{
Courses.Remove(c);
c.Enrolled.Remove(this); // mutates Course's collection
}
}The cleanup: pick one owner of the enrolment relationship (say, Course), make collections private, and converse through methods:
public class Course
{
private readonly List<Student> _enrolled = new();
private readonly int _capacity = 30;
public bool Enroll(Student s)
{
if (_enrolled.Count >= _capacity) return false;
_enrolled.Add(s);
s.RecordCredits(4); // a request, not a trespass
return true;
}
public void Withdraw(Student s)
{
if (_enrolled.Remove(s)) s.RecordCredits(-4);
}
}
public class Student
{
private int _creditsTaken;
public void RecordCredits(int delta) => _creditsTaken += delta;
}Now Student guards its own credits — it could add a rule like "never above 24" in one place. C# also offers internal access: if two classes genuinely form one module, you may sanction controlled closeness inside the assembly while staying private to the outer world. Controlled, declared intimacy inside a module is design; secret mutual poking across modules is the smell.
College corner: you can measure this smell in a repository without reading a single class. Mine the version history for logical coupling (also called change coupling or co-change): count how often two files appear in the same commit. Pairs with very high co-change but no declared dependency between their modules are prime intimacy suspects. Combine that with structural metrics — mutual CBO (each class counted in the other's coupling), and instability I = Ce / (Ca + Ce) from Robert Martin's package metrics — and you get a ranked list of tangled pairs. Many teams run exactly this analysis before a big refactoring sprint: history tells you where the kitchens are shared.
🏢 Where this smell hides in real projects
1. ORM entities with bidirectional navigation. Entity Framework and Hibernate make two-way links easy: Order.Customer and Customer.Orders. Convenient for queries — but application code starts mutating both ends by hand, and the pair becomes inseparable. Many teams now prefer one-directional references and aggregate roots (one boss object that owns its children) exactly to prevent this.
2. Parent–child UI components. A screen widget that reaches into its child's internal state, while the child calls back up and flips the parent's flags. Change the parent layout, break the child; change the child, break the parent. Component frameworks push props-down/events-up patterns to keep this one-way.
3. A class and its "best friend" helper. A ReportGenerator plus a ReportGeneratorHelper that share fields through internal access and call each other back and forth. They are one class pretending to be two. Either merge them honestly or separate them properly.
4. Subclass–superclass back doors. A base Controller with a dozen protected fields that every subclass freely mutates. The protected state is a shared kitchen for the whole family hierarchy. This is Refused Bequest's cousin, and Replace Inheritance with Delegation is the exit.
5. Test code that mirrors internals. Tests that reach into private state via reflection or @VisibleForTesting back doors are intimate with the production class. When refactoring breaks fifty tests without breaking behaviour, intimacy was the cause.
6. A web of mutually-known managers. When several "manager" classes all hold references to each other and call each other freely, you have colony-wide intimacy. The Mediator pattern replaces this web with a hub through which everyone communicates.
🤔 When it is okay to ignore
Some closeness is by design, and breaking it would be wrong. Judge honestly:
| Situation | Verdict | Reason |
|---|---|---|
| Iterator and its collection | Acceptable intimacy | They form one concept; the iterator must know the collection's structure to walk it |
| Builder and the product it assembles | Acceptable | The builder exists to set up the product's internals; after build(), the closeness ends |
Two classes in one module using internal / package-private scope | Acceptable if deliberate | Languages provide these scopes to sanction controlled intimacy, hidden from the wider world |
| Two classes that always ship, change, and version together as one unit | Tolerable | If they truly are one unit of change, the cost is low — but keep the shared surface minimal |
| Two classes in different modules mutating each other's fields | Fix it | Cross-module intimacy blocks reuse, testing, and independent change |
| Subclass depending on parent's exact internal layout | Fix it | The inheritance contract is broken; use delegation |
The smell's name says it precisely: it is inappropriate intimacy that we cure, not all intimacy. Ask one question: "Do these two classes ever need to change, ship, or be tested separately?" If yes — and it is almost always yes across module boundaries — then their closeness is inappropriate, and every shared internal will some day send its bill.
💊 Which refactorings cure it
| Refactoring | When to use it | What it does |
|---|---|---|
| Move Method / Move Field | Behaviour and data live on opposite sides | Reunites data with the code that uses it, removing the reach-across |
| Extract Class | The intimacy hides a missing shared concept | Creates a third class both can depend on cleanly |
| Hide Delegate | One class navigates through the other into a third | Replaces navigation with a polite question — Law of Demeter restored |
| Change Bidirectional Association to Unidirectional | A back-reference exists but is not truly needed | Removes the spare key and the temptation that comes with it |
| Replace Inheritance with Delegation | Subclass is intimate with parent internals | Turns the back-door "is-a" into a clean "has-a" through an interface |
📦 Quick revision box
+=================================================================+
| INAPPROPRIATE INTIMACY — QUICK REVISION |
+=================================================================+
| STORY : Mrs. Sharma & Mrs. Verma rearrange each other's |
| kitchens. Neither can renovate. No privacy, |
| total dependency. |
| |
| SMELL : Two classes read AND WRITE each other's internals, |
| often with references in both directions. |
| |
| LAW : Law of Demeter — "talk only to your immediate |
| friends." Ask for what you need; never walk in |
| and take it. |
| |
| SPOT IT : paired commits, bidirectional refs, fields exposed |
| only for the partner, untestable alone |
| |
| CURE : decide ownership -> Move Method / Move Field |
| -> cut the back-reference -> make fields private |
| -> Hide Delegate for leftover reaching |
| -> Extract Class if a shared concept appears |
| |
| IGNORE : iterator+collection, builder+product, |
| declared internal/friend scope within one module |
| |
| RULE : Collaborate through small polite interfaces, |
| never through each other's private state. |
+=================================================================+✏️ Practice exercise
Here are two tangled classes from a school sports app:
class Team {
players: Player[] = [];
totalSkill = 0;
recruit(p: Player) {
this.players.push(p);
p.team = this; // writes Player's field
p.isCaptain = this.players.length === 1; // writes Player's field
this.totalSkill += p.skill; // reads Player's field
}
}
class Player {
skill = 0;
team: Team | null = null;
isCaptain = false;
quit() {
if (!this.team) return;
this.team.totalSkill -= this.skill; // writes Team's field
const i = this.team.players.indexOf(this);
this.team.players.splice(i, 1); // mutates Team's list
if (this.isCaptain && this.team.players.length > 0) {
this.team.players[0].isCaptain = true; // writes ANOTHER player's field!
}
}
}Your tasks:
- List the trespasses. Write down every line where one class reads or writes another object's field. How many are reads and how many are writes? (Hint: there is even a player writing into another player's house.)
- Find the Law of Demeter violations. In
quit(), which objects are "close friends" of the method, and which are strangers it touches anyway? Use the formal statement from the College corner if you want full marks. - Decide ownership. Who should own the player list and the captain choice —
TeamorPlayer? Write one sentence of justification. - Plot the pair. On Figure 3's chart, where does the
Team–Playerpair sit before your refactoring? Where should it sit after? - Refactor. Rewrite the pair so that:
Teamhasrecruit(p)andrelease(p)and owns captaincy;Playerexposesskill()as a method and holds no back-reference toTeam. All fields private. Callers sayteam.release(player)instead ofplayer.quit(). - Test the result. Write (or just describe) a unit test for
Team.releasethat uses aPlayerwith skill 7. Could you have written this test against the old code without a full realTeaminside thePlayer? Why not? - Bonus. Suppose the captain-selection rule becomes complicated (seniority, attendance, coach's choice). Would you keep it inside
Team, or extract aCaptainPolicyclass? Which refactoring from this post is that?
If your refactored code has zero writes across class boundaries, both kitchens are private again. Shabash!
Frequently asked questions
- What is the Inappropriate Intimacy code smell?
- It is when two classes know far too much about each other's internal details. They read and write each other's fields, depend on each other's private structure, and often hold references to each other in both directions. Changing one class then forces changes in the other — they have become one class wearing two names.
- How is Inappropriate Intimacy different from Feature Envy?
- Feature Envy is one-directional: a single method in class A keeps using class B's data. Inappropriate Intimacy is the mutual, grown-up version: A pokes into B and B pokes back into A. Envy is one nosy neighbour; intimacy is two neighbours rearranging each other's kitchens.
- What is the Law of Demeter in simple words?
- Talk only to your close friends, not to strangers. A method should call methods on its own object, its own fields, its parameters, and objects it creates — but it should not reach through a friend into the friend's internal objects. Ask for what you need; do not go inside and take it.
- Which refactorings fix Inappropriate Intimacy?
- Move Method and Move Field put data and behaviour in the same class so reaching-across stops. Extract Class pulls out a shared concept both classes were fighting over. Hide Delegate replaces deep reaching with a polite question. Change Bidirectional Association to Unidirectional removes the unneeded back-reference. For a subclass too cosy with its parent, Replace Inheritance with Delegation.
- Is some intimacy between classes acceptable?
- Yes. An iterator and its collection, a builder and its product — some pairs form one cohesive concept and legitimately know each other well. Languages even provide friend, internal, and package-private scoping to sanction controlled intimacy inside a module. The smell is inappropriate intimacy, not all intimacy.
Further reading
Related Lessons
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.
Message Chains: Asking a Friend, Who Asks a Cousin, Who Asks an Uncle
Learn the Message Chains code smell with a story about finding out if bread is available — through four people. When code reads a.getB().getC().getD(), the caller is coupled to a whole path. Learn the Law of Demeter and cure chains with Hide Delegate.
Middle Man: The Helper Who Only Forwards Your Message to the Principal
Learn the Middle Man code smell with a story of a school helper who only carries messages without adding anything. When a class merely forwards every call, remove it — but learn why Proxy, Facade, and Adapter are middle men ON PURPOSE.
Move Method: Shift Work to the Class Where It Truly Belongs
Learn the Move Method refactoring through a simple school story. Shift a method into the class whose data it uses most so behaviour and data stay together.