Skip to main content
CleanCodeMastery

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.

24 min read Updated June 11, 2026beginner
code-smellscouplersmiddle-mandelegationremove-middle-manhide-delegatetypescriptcsharp

📨 The helper who only carried the chit

In your school there is a peon, Shankar bhaiya, who sits outside the principal's office on a wooden stool. One day the school made a rule: nobody talks to the principal directly. Want to ask for a leave? Write a chit, give it to Shankar bhaiya. He walks ten steps, puts the chit on the principal's desk. The principal writes "Approved", and Shankar bhaiya walks ten steps back and hands it to you.

Here is the part nobody remembers: Shankar bhaiya once had a real job. Years ago, he kept a register — who met the principal, at what time, for what reason. He turned away students whose forms were incomplete. He batched the chits so the principal saw them all at 2 p.m. instead of being disturbed all day. The stool, the rule, the position — all of it made sense then.

Then the school computerised the visitor register. The form-checking moved to the class teachers. The 2 p.m. batching stopped when the principal got an inbox tray. One by one, every real duty walked away. But Shankar bhaiya stayed on the stool, and the rule stayed on the noticeboard. Now ask the honest question: what does Shankar bhaiya add today?

He does not check whether your leave form is complete. He does not stop troublemakers. He does not translate, batch, note, or filter anything. He carries paper ten steps, both ways. If he goes on holiday, all requests stop — the school discovered this last Diwali, when forty chits piled up on an empty stool. When the principal started accepting sports applications too, Shankar bhaiya had to also learn to carry sports applications — two changes for one new facility. Everyone waits in his queue... for nothing.

Figure 1: One leave request, lived minute by minute — feel the hop that adds nothing

Compare him with Ram Singh, the watchman from our Proxy pattern story. Ram Singh also stands between you and the owner — but he checks the visitor list, refuses strangers, notes entry times. He adds protection. A receptionist who fixes appointments adds scheduling. A translator adds translation. These middle people earn their place.

Shankar bhaiya earns nothing — today. He is pure forwarding. Why not talk to the principal directly? In code, a class like him has a name: the Middle Man smell — a class whose methods do nothing but pass calls along to another object.

🔍 What is this smell?

A Middle Man is a class where most methods are one-line forwarders:

doThing(x: number) { return this.delegate.doThing(x); }

Martin Fowler describes it in Refactoring: you look at a class's interface and find half its methods simply delegating to another class. Refactoring Guru's catalog asks the blunt question — if a class performs only one action, delegating work to another class, why does it exist at all?

Be very careful with the definition, because this is the most balance-needing smell in the whole Couplers family. The smell is not "a class that delegates". Delegation is one of the healthiest tools in object-oriented design — we used it gladly in Message Chains when applying Hide Delegate. The smell is a class that delegates and adds nothing: no decision, no transformation, no protection, no simplification. An empty corridor that every call must walk through.

💡

The one-question test for any delegating class: "If I deleted this class and connected callers directly to the real object, what would we lose?" If the answer is "nothing", you have a Middle Man. If the answer is "we would lose the caching / the permission check / the simple stable interface / the compatibility conversion" — the class is earning its keep. Judge forwarding by what it ADDS, not by the forwarding itself.

College corner: there is a famous aphorism by David Wheeler, one of computing's first PhDs: "All problems in computer science can be solved by another level of indirection." The less-quoted corollary, attributed to Kevlin Henney among others, completes it: "...except the problem of too many levels of indirection." Formally, every indirection layer adds to the system's fan-out depth — the number of modules a request traverses — and raises comprehension cost (a reader must open one more file per layer) without necessarily raising abstraction level (the interface may be a one-to-one mirror, abstracting nothing). A useful metric pair: count a class's delegation ratio (forwarding methods divided by total methods) against its added-behaviour count (decisions, transformations, guards). High ratio + zero added behaviour = Middle Man, mathematically.

🐣 Where do Middle Men come from?

Three common birth stories, and all three are innocent:

  1. The over-corrected Message Chain. To stop callers from writing a.getB().getC().getD(), someone applied Hide Delegate — good! Then applied it again, and again, for every hop everywhere. The middle classes filled up with forwarders until forwarding became all they do. The cure for one smell, taken too far, became the other smell. Fowler states it plainly: Remove Middle Man is the inverse of Hide Delegate.
  2. The class that outlived its job. A wrapper was added to do logging, or a retry, or a permission check. Later the logging moved to middleware and the check moved to the gateway — but the wrapper stayed, forwarding calls for no reason, because every client already depends on its interface. This is Shankar bhaiya's exact biography.
  3. Speculative indirection. "Let us add a layer in case we need to swap the implementation someday." Someday never comes. The layer sits there, charging rent and paying nothing. (Its cousin smell: Speculative Generality.)

The second story is so common that it deserves a state diagram. Most Middle Men are not born — they are left behind:

Figure 2: The life cycle of a wrapper — real job, job moves away, empty shell remains

Notice the self-loop on EmptyShell. That is the dangerous state — the class can sit there for years, surviving on appearance alone.

👀 How to spot it

The checklist:

  • Open the class. Are most methods one-liners of the form return _delegate.same(args);?
  • Does its public interface mirror another class almost method-for-method, like tracing paper over someone else's drawing?
  • To add one feature, must you edit two places — the real method on the delegate and a forwarding method here?
  • When debugging, do you always step through this class without ever stopping in it, because nothing ever happens here?
  • Did the class once have a real job (caching, checks, logging) that has since moved away, leaving the shell?
  • Does the class make zero decisions? No if, no transformation, no validation, no error handling — just relay?

Fowler's rough yardstick: if about half the methods are pure delegation, suspect a Middle Man. For our school clerk below, the honest audit looks like this:

Figure 3: Audit of the clerk's methods — when this pie is almost all forwarding, the class is a stool in a corridor

Use this severity table:

What you find in the classVerdict
A few forwarders + several methods doing real workHealthy — normal delegation inside a working class
Forwarders that add a check, default, conversion, or logHealthy — each forward earns its hop
Half the methods are pure pass-throughSuspect — ask the one-question test
Nearly all methods are pass-through, no decisions anywhereMiddle Man — remove or give it a real job
Pass-through everything, and the class is also tiny and aimlessMiddle Man + Lazy Class — delete with confidence
Forwarding with a declared purpose: access control, caching, simplification, interface conversionA pattern, not a smell — Proxy / Facade / Adapter / Decorator (see below)

The same judgement as a chart. Two axes decide everything: how much of the interface is forwarded, and what each forward adds:

Figure 4: Is this delegation healthy? Forward everything while adding nothing, and you land in the smell corner

Wait — the smell corner is bottom-right on this chart? Look again. The x-axis is value added, the y-axis is how much gets forwarded. A class that forwards everything and adds checks (top-right) is a fine Proxy. A class that forwards everything and adds nothing (top-left) is the smell — labelled here as quadrant 4 by Mermaid's numbering. The lesson stands either way: the forwarding axis never convicts a class by itself; only the value axis does.

⚠️ Why it is a problem

1. Indirection without value. Every call pays an extra hop, every reader opens an extra file — and the hop buys nothing. In design we say: every layer of indirection must justify itself. This one cannot.

2. Double maintenance forever. The delegate grows a new method; the middle man must mirror it. Forget once, and callers cannot reach the new feature. The two interfaces drift out of sync, and keeping them aligned becomes someone's permanent chore. One behaviour, two edits — for the rest of the project's life.

3. It hides the real design. A new programmer reads controller → service → repository and assumes three responsibilities exist. After tracing for an hour, she discovers the service was hollow all along — the real conversation was controller-to-repository. The empty layer lied about the architecture.

4. False reassurance makes it permanent. The layer looks like serious structure. People hesitate to delete what looks load-bearing. So the dead weight ossifies, gets copied into the next module as "our standard pattern", and multiplies.

Cost number 2 is worth graphing, because it compounds with every hollow layer stacked in the path:

Figure 5: Edits needed to ship one new feature — each hollow layer adds one more place to mirror it
Figure 6: Every request walks through the middle man — and the middle column adds nothing

Look at the middle box. Cover it with your thumb, connect the arrows directly — does anything change? No. That thumb test is the refactoring we are about to do.

⚠️

Watch out for the cargo-cult version: "every controller MUST call a service, every service MUST call a repository" — applied even when the service has nothing to do. Rules like this manufacture Middle Men at industrial scale. Layers are for responsibilities, not for ceremony. If a layer has no responsibility in this particular flow, the flow may skip it.

💻 A real-life code example

Here is Shankar bhaiya as TypeScript. A StudentOfficeClerk who stands between teachers and the records system:

class StudentRecords {
  find(id: number): Student | undefined { /* real lookup */ return undefined; }
  save(s: Student): void { /* real persistence */ }
  remove(id: number): void { /* real deletion */ }
  attendanceOf(id: number): number { /* real query */ return 92; }
}
 
// The Middle Man — count the decisions made below: zero.
class StudentOfficeClerk {
  constructor(private records: StudentRecords) {}
 
  find(id: number)            { return this.records.find(id); }
  save(s: Student)            { return this.records.save(s); }
  remove(id: number)          { return this.records.remove(id); }
  attendanceOf(id: number)    { return this.records.attendanceOf(id); }
}
 
// Every caller in the school goes through the clerk:
class ClassTeacher {
  constructor(private clerk: StudentOfficeClerk) {}
 
  markLongAbsent(id: number): string {
    const attendance = this.clerk.attendanceOf(id);
    return attendance < 75 ? "Send letter to parents" : "OK";
  }
}

Read StudentOfficeClerk line by line and search for one if, one transformation, one check, one log. There is nothing. Its interface is a photocopy of StudentRecords. Trace a single call and watch the chit walk ten steps for no reason:

Figure 7: One attendance query, traced — the clerk repeats the question and repeats the answer, unchanged

The mirroring is even clearer as a class diagram — the clerk's interface is tracing paper over the records system:

Figure 8: Method-for-method mirror — the photocopied interface is the smell's fingerprint

The day StudentRecords gains feesPendingOf(id), somebody must remember to add a fifth photocopy line to the clerk — or teachers cannot ask about fees.

And the class looks important! It has a respectable name, a constructor, dependency injection. A reviewer skimming the file list would assume it does clerical validation. That false impression is exactly cost number 3 and 4 from above.

For completeness, here is the same hollow layer in Python — it is, if anything, easier to write there:

# Smelly: a "service" that photocopies the repository
class StudentService:
    def __init__(self, records):
        self._records = records
 
    def find(self, sid):          return self._records.find(sid)
    def save(self, student):      return self._records.save(student)
    def remove(self, sid):        return self._records.remove(sid)
    def attendance_of(self, sid): return self._records.attendance_of(sid)

Four lines, four forwards, zero decisions. In Python such classes often hide behind the excuse "it gives us a place to add logic later". Later has a way of never coming — and when it does come, it usually belongs in a specific method, not a whole photocopied layer.

🛠️ Cleaning it up, step by step

The cure is Remove Middle Man — let callers talk to the principal directly. We go gently, step by step, because many callers depend on the middle man's interface.

Step 1 — Run the one-question test. Delete the clerk in your head. What is lost? Nothing — no check, no cache, no simplification. Confirmed Middle Man. (If you had found even one method doing real work — say save also validated the student — the plan changes: keep the class for its real work and inline only the empty forwarders using Inline Method.)

Step 2 — Expose the delegate. Give callers a way to reach the real object. Often the simplest move is to inject StudentRecords directly where StudentOfficeClerk was injected:

class ClassTeacher {
  constructor(private records: StudentRecords) {}   // direct line now
 
  markLongAbsent(id: number): string {
    const attendance = this.records.attendanceOf(id);
    return attendance < 75 ? "Send letter to parents" : "OK";
  }
}

Step 3 — Migrate callers one at a time. Do not delete the clerk in one big bang. Move one caller, compile, test, commit. Move the next. The clerk's photocopied interface means each migration is a mechanical rename — low risk, steady progress.

Step 4 — Delete the empty shell. When the last caller has moved, StudentOfficeClerk has zero references. Delete the file. Enjoy the moment — deleting code is one of programming's purest joys.

Step 5 — If the middle man was almost its delegate, consider inheritance. When a class forwards nearly the whole interface of a genuinely related class, Replace Delegation with Inheritance is sometimes cleaner: make it a subclass, and all those hand-written forwarders disappear because the methods are simply inherited. Use this only when the "is-a" relationship is honestly true — never just to save typing.

Step 6 — Keep the balance in mind for next time. Remember where many Middle Men come from: over-applying Hide Delegate while fighting Message Chains. The two refactorings are inverses; the two smells are opposite ditches. A few forwarding methods that protect callers from a changing structure = good delegation. A class that is only forwarding = Middle Man. You will adjust this balance many times in your career, in both directions, and that is normal.

Figure 9: The balance beam — Message Chains on one side, Middle Man on the other, healthy delegation in the middle

College corner: the balance beam has a name in architecture literature: choosing the right level of indirection is a trade between coupling (chains couple callers to structure) and cognitive distance (layers make readers traverse more files). A practical heuristic used in large codebases: an indirection layer must satisfy at least one of three justifications — it varies independently (you genuinely swap implementations), it guards an invariant (validation, permissions, transactions), or it stabilises a boundary (its interface changes slower than what is behind it). A layer satisfying none of the three is, by definition, a Middle Man — no matter how official its name sounds in the architecture diagram.

🧪 The same smell in C#

The most famous C# middle man is the hollow service layer:

// Smelly: a service that only photocopies the repository
public class UserService
{
    private readonly UserRepository _repo;
    public UserService(UserRepository repo) => _repo = repo;
 
    public User Find(int id)    => _repo.Find(id);
    public void Save(User u)    => _repo.Save(u);
    public void Delete(int id)  => _repo.Delete(id);
    public bool Exists(int id)  => _repo.Exists(id);
}

After Remove Middle Man, callers hold the real thing:

public class RegistrationController
{
    private readonly UserRepository _repo;
    public RegistrationController(UserRepository repo) => _repo = repo;
 
    public void Register(User user)
    {
        if (!_repo.Exists(user.Id))
            _repo.Save(user);
    }
}

But contrast it with a service that earns its place — same shape, different soul:

// NOT a middle man: every method adds a decision or protection
public class UserService
{
    private readonly UserRepository _repo;
    private readonly IAuditLog _audit;
 
    public User Find(int id) =>
        _repo.Find(id) ?? throw new UserNotFoundException(id);   // adds a guarantee
 
    public void Save(User u)
    {
        u.Validate();                                            // adds validation
        _repo.Save(u);
        _audit.Record("user.saved", u.Id);                       // adds auditing
    }
}

Same arrows in a diagram, opposite verdicts. The smell was never the arrow — it was the emptiness around it.

🏢 Where this smell hides in real projects

1. Anemic service layers. The XService that photocopies XRepository, found in thousands of codebases because "we always have a service layer". Where the service genuinely orchestrates — transactions, validation, multiple repositories — it is real. Where it forwards CRUD one-to-one, it is Shankar bhaiya with dependency injection.

2. Wrappers that outlived their feature. The CachingProductClient whose cache was removed during a redis migration; the RetryingHttpService whose retry moved into a resilience library. The adjective in the class name is now a lie, and only forwarding remains.

3. Manager-of-a-manager chains. TaskManager calls TaskCoordinator which calls TaskExecutor — and only the last one contains logic. Enterprise codebases sometimes stack three hollow floors above one working basement.

4. Wrapper-around-the-framework, abandoned. A team wraps a logging or HTTP library "so we can swap it later", but the wrapper exposes the library's exact same methods one-to-one. Interface photocopying provides zero swap-protection — if the shapes are identical, swapping the inside breaks everything anyway. Either design a real abstraction (different, smaller interface — that is an Adapter or Facade) or use the library directly.

5. Over-Demeter'd domain models. After an enthusiastic Law-of-Demeter cleanup, Order has thirty methods, twenty-five of which forward to Customer, Address, and Invoice. The chains are gone — and Order has become a switchboard. The balance beam tipped past the middle.

6. Re-export barrels and god interfaces. Modules that exist only to re-expose another module's API one-to-one. A small curated facade is useful; a full mirror is maintenance debt.

🤔 When it is okay to ignore

This is the most important section of this post. Some middle men are hired on purpose. Four of our structural design patterns are, structurally, middle men — and they are excellent design, because their forwarding adds something:

Delegating classWhat the forwarding ADDSVerdict
ProxyAccess control, lazy loading, caching, logging — decides whether/when the call goes throughKeep — pattern, not smell
FacadeOne simple, stable entry point hiding a messy subsystem; callers stop depending on internalsKeep — pattern, not smell
AdapterConverts one interface into another so incompatible classes can work togetherKeep — pattern, not smell
DecoratorLayers extra behaviour on some calls; pass-through of the rest is intentionalKeep — pattern, not smell
Forwarders left by a sensible Hide Delegate over an unstable structureA stable boundary that absorbs model changesKeep — this is the Demeter trade working
API gateway / anti-corruption layer at a system boundaryProtection of your model from an external system's shapeKeep
A class whose every method relays unchanged, adding none of the aboveNothingRemove Middle Man

How can the same structure be a celebrated pattern in one post and a smell in another? Because structure is not intent. Ram Singh and Shankar bhaiya both stand between you and an important person. One filters, protects, and records; the other only walks chits. The diagram looks identical; the value added per hop is opposite. This is why you must never hunt smells by shape alone — always ask what the layer does, not where it stands.

Hold the whole judgement in one map:

Figure 10: The Middle Man judgement map — signs, causes, cures, and the forwarding that earns its keep
ℹ️

A note on the Law of Demeter trade-off, to connect the last three posts. Demeter ("talk only to your immediate friends") pushes you to ADD delegating methods, so callers stop navigating object graphs — that cures Message Chains and reduces Inappropriate Intimacy. The Middle Man smell pushes back: do not let delegation become the only thing a class does. Neither rule wins everywhere. The skill you are building is judging, case by case, whether a hop earns its cost.

💊 Which refactorings cure it

RefactoringWhen to use itWhat it does
Remove Middle ManThe class is mostly/entirely pass-throughCallers talk to the delegate directly; the empty layer is deleted
Inline MethodOnly a few forwarders pollute an otherwise-working classFolds trivial forwarders into their callers; the class keeps its real work
Replace Delegation with InheritanceThe middle man forwards nearly all of a truly related classInherited methods replace hand-written forwarders — only when "is-a" is honest
Hide Delegate(The inverse!) You removed too much and chains reappearedAdds a purposeful forwarder back — the balance beam tips both ways

📦 Quick revision box

+=================================================================+
|                   MIDDLE MAN — QUICK REVISION                    |
+=================================================================+
| STORY    : Shankar bhaiya only carries chits to the principal.   |
|            No checking, no protecting, no translating.           |
|            Why not talk to the principal directly?               |
|                                                                  |
| SMELL    : A class whose methods mostly just forward:            |
|            doThing(x) { return delegate.doThing(x); }            |
|            Fowler's hint: ~half the methods delegating.          |
|                                                                  |
| COSTS    : pointless hop, double maintenance (every new          |
|            feature = 2 edits), hidden real design,               |
|            false look of importance                              |
|                                                                  |
| TEST     : "Delete it mentally — what would we lose?"            |
|            Nothing lost  -> Middle Man, remove it                |
|            Check/cache/simplify/convert lost -> keep it!         |
|                                                                  |
| CURE     : Remove Middle Man (callers go direct)                 |
|            Inline Method (few forwarders only)                   |
|            Replace Delegation with Inheritance (honest is-a)     |
|                                                                  |
| NOT A    : Proxy (controls access), Facade (simplifies),         |
| SMELL    : Adapter (converts), Decorator (adds behaviour)        |
|            — these forward ON PURPOSE and earn every hop.        |
|                                                                  |
| BALANCE  : Hide Delegate <-> Remove Middle Man are inverses.     |
|            Message Chains and Middle Man are opposite ditches;   |
|            healthy delegation walks between them.                |
+=================================================================+

✏️ Practice exercise

Here is a food-delivery codebase with three delegating classes. Your job: judge each one.

class KitchenSystem {
  prepare(orderId: number): void { /* real cooking workflow */ }
  estimateMinutes(orderId: number): number { return 25; }
  cancel(orderId: number): void { /* real cancellation */ }
}
 
// Class 1
class KitchenCoordinator {
  constructor(private kitchen: KitchenSystem) {}
  prepare(id: number)         { return this.kitchen.prepare(id); }
  estimateMinutes(id: number) { return this.kitchen.estimateMinutes(id); }
  cancel(id: number)          { return this.kitchen.cancel(id); }
}
 
// Class 2
class KitchenGate {
  constructor(private kitchen: KitchenSystem, private auth: AuthService) {}
  cancel(id: number, staff: Staff) {
    if (!this.auth.canCancel(staff)) throw new Error("Not allowed");
    this.kitchen.cancel(id);
  }
  prepare(id: number) { return this.kitchen.prepare(id); }
}
 
// Class 3
class LegacyPosAdapter {
  constructor(private kitchen: KitchenSystem) {}
  // The 20-year-old POS terminal sends strings like "PREP|1042"
  handle(message: string) {
    const [cmd, id] = message.split("|");
    if (cmd === "PREP") this.kitchen.prepare(Number(id));
    if (cmd === "CANC") this.kitchen.cancel(Number(id));
  }
}

Your tasks:

  1. Apply the one-question test to each class. "If deleted, what would we lose?" Write one line per class.
  2. Plot all three on Figure 4's chart. Estimate each class's position on the two axes (value added, fraction forwarded). Which quadrant does each land in?
  3. Name the verdicts. Which is a true Middle Man, which is a (partial) protection Proxy, and which is an Adapter? Note that one class is mixed — it has one earning method and one pure forwarder. What would you do with just that forwarder? (Hint: Inline Method.)
  4. Refactor Class 1. Describe the step-by-step Remove Middle Man: which callers change first, what gets deleted last?
  5. Defend Class 3 in one sentence to a teammate who says "it just forwards to the kitchen, delete it."
  6. The balance question. Six months later, callers everywhere are writing kitchen.scheduler().queue().position(orderId) — a fresh Message Chain. Which refactoring do you reach for now, and what smell must you avoid re-creating while applying it?
  7. Bonus. Your team's rulebook says "controllers must never touch repositories directly; always go through a service." Write two sentences: one defending the rule, one explaining when it manufactures Middle Men. (There is no single right answer — this is the judgement the whole post is about.)

If you gave three different verdicts to three structurally similar classes, you have learnt the deepest lesson here: judge layers by what they add, not by how they look. Ekdum first class!

Frequently asked questions

What is the Middle Man code smell?
A class whose methods do almost nothing except forward calls to another object — one-liners like return delegate.doThing(). Fowler's rough rule: if around half of a class's methods are pure delegation, suspect a Middle Man. The layer adds an extra hop but no decision, no transformation, and no protection.
How do I fix a Middle Man?
Use Remove Middle Man: let clients talk to the real object directly and delete the forwarding layer. If only a few forwarders exist, Inline Method folds them into callers. If the class forwards nearly the whole interface of a related class, Replace Delegation with Inheritance can make the inherited methods do the job.
Are Proxy, Facade, and Adapter middle men?
They forward calls, but they are NOT the smell — they forward for a reason. A Proxy controls access (lazy loading, caching, permissions), a Facade gives one simple stable entry to a messy subsystem, an Adapter converts one interface into another, and a Decorator adds behaviour around calls. Judge a delegating class by what the forwarding ADDS. If it adds a decision, transformation, protection, or stable boundary, keep it.
How are Middle Man and Message Chains related?
They are opposite ditches on the same road. Message Chains happen when callers navigate through objects themselves; the cure, Hide Delegate, adds forwarding methods. Overdo that cure and you manufacture Middle Men — classes that only forward. Remove Middle Man is literally the inverse refactoring of Hide Delegate. Healthy design balances between the two.
Why do useless middle-man classes survive so long in codebases?
Three reasons: every client depends on their interface, so deleting them needs many small edits; they look like meaningful architecture, so people assume they do something; and they often once had a real job (logging, caching, checks) that was removed later, leaving the empty shell behind. The class outlived its purpose but kept its position.

Further reading

Related Lessons