Skip to main content
CleanCodeMastery

Inline Method: Remove the Shortcut That Was Never a Shortcut

Learn Inline Method step by step. When a tiny method's body is clearer than its name, fold it back into the caller and remove one useless hop from your code.

22 min read Updated June 11, 2026beginner
refactoringcomposing methodsinline methodinline functionindirectionclean code

The Corridor "Shortcut" That Was Longer ๐Ÿƒ

In Aman's new school, the seniors love giving directions. On his first day, Aman asks Rohan, "How do I reach the library?" Rohan grins and says, "Bhai, take the shortcut! Go through Corridor B, climb the stairs near the science lab, walk across the first floor, then come down the stairs near the staff room. Library will be right there."

Aman follows the route faithfully. Up the stairs, school bag bouncing. Across the long first floor, past the computer lab, past the art room. Down the other stairs, two at a time. It takes him six minutes, and he reaches the library sweating, just as the bell rings.

Next day, Mrs. Iyer the librarian sees him puffing at the door and asks why he came from upstairs. Aman proudly explains Rohan's shortcut. Mrs. Iyer laughs, walks him back out, and points down the ground-floor corridor. "Beta, the library is straight down this corridor. Thirty seconds. Who told you to climb two staircases?"

Rohan's "shortcut" was not a shortcut at all. It was an extra journey โ€” up, across, and down โ€” that ended exactly where a straight walk would have ended. The route had more steps, more turns, and more chances to get lost, but gave nothing extra in return. Worse, Aman had already taught the same route to two other juniors. Now three boys climb stairs every day for no reason.

Figure 1: Aman's two journeys to the same library

Code collects routes like this too. A method calls a second method, which only calls a third method, which finally does the real one-line work. Every reader of the code must "climb the stairs and come down" โ€” jump from method to method โ€” just to discover something they could have read in one straight line. Each useless hop is a small tax on understanding. And just like Aman teaching juniors, bad routes spread: new developers copy the pattern, and the staircase grows longer every year.

The Inline Method refactoring is Mrs. Iyer pointing down the corridor. It removes the detour. And remember the golden rule: refactoring means improving the inside of code without changing what it does outside. The reader's journey gets shorter; the program's behaviour stays exactly the same. The library was always in the same place โ€” we only fixed the route.

What is Inline Method?

Here is the plain definition.

When a method's body is just as clear as its name โ€” or even clearer โ€” take the body, paste it directly into every place that calls the method, and then delete the method. The indirection disappears, and the logic becomes visible right where it is used.

Indirection (calling through layers) is a tool, not a virtue. Every method is a little promise: "my name tells you something useful, so you do not need to read my body." When the name stops keeping that promise โ€” when reading the name and reading the body give you exactly the same information โ€” the method is charging rent without providing a service. Evict it.

๐Ÿ’ก

One-line memory trick: Inline Method = if the body says it better than the name, put the body where the calls are and delete the method. It is the exact inverse of Extract Method.

A note about the name. In Martin Fowler's Refactoring 1st edition (1999) this was called Inline Method. In the 2nd edition (2018) he renamed it Inline Function, for the same reason Extract Method became Extract Function: the technique applies to standalone functions too, not only class methods. On refactoring.com you will find it under Inline Function; Refactoring.Guru and most IDE menus still say Inline Method. Same refactoring, two names.

Fowler's framing is worth quoting in spirit: he uses Inline Function when a function's body is as clear as its name, and also when a group of functions has been badly factored โ€” he inlines them all into one big function and then re-extracts the pieces properly. So this refactoring is both a cleaner and a reset button.

Here is the whole topic on one map. Come back to it after the post and check you can explain every branch:

Figure 2: Inline Method in one mind map โ€” when, how, and what it cures

When Do We Need It? ๐Ÿ”

Watch for these situations. Each one is a staircase pretending to be a corridor.

  1. The name is a literal restatement of the body. A method isWeekend() returning day === "Sat" || day === "Sun" earns its keep โ€” the name compresses an idea. But moreThanFiveLateDeliveries() returning lateDeliveries > 5 repeats the body word for word. The reader still jumps to the body "just to be sure," and gains nothing. Inline it.
  2. A chain of tiny delegating methods. Method A calls B, B calls C, C does one line of work. This is Rohan's staircase route, written in code. Often this happens slowly, as code is moved around over months and the old wrappers are never cleaned up. Nobody designed the staircase; it grew.
  3. A Middle Man. A class or method whose only job is forwarding calls to another object. Inlining the forwarders lets callers talk to the real worker directly โ€” this is the heart of Remove Middle Man.
  4. Speculative Generality. Someone added wrapper methods "in case we need flexibility later." Later never came. The wrappers are just stairs to climb. Inline them. Rohan probably believed his route was genuinely better โ€” most useless indirection is added with good intentions.
  5. Preparing to re-extract. This one is clever. Suppose a function was split into helpers, but the helpers were cut along bad lines โ€” each helper does half of two jobs instead of all of one job. The easiest fix is often: inline everything back into one big function, look at the whole picture calmly, and then use Extract Method to re-cut along better lines. Inline Method clears the ground so Extract Method can rebuild properly.

So Inline Method is not an attack on small methods. It is a quality check on names. Methods whose names add meaning stay. Methods whose names only add hops go.

When you actually open a class that has grown a forwarding habit, the line count tells a sad story. Very little of the code does real work; most of it is corridor:

Figure 3: In a forwarding-heavy class, most lines add hops, not logic

Nearly three-quarters of that class is travel, not destination. Inline Method shrinks the travel to zero and leaves the destination standing.

College corner: Here is a beautiful irony for CS students. Compilers perform Inline Method automatically, all day, every day โ€” they just call it inlining or inline expansion. The JVM's HotSpot JIT inlines small hot methods (trivial methods under roughly 35 bytecodes are inlined almost unconditionally), .NET's RyuJIT does the same for small IL bodies, and C++ compilers at -O2 inline so aggressively that the inline keyword is today mostly a linkage hint, not a performance command. Inlining is often called the "mother of all optimisations" because it unlocks others: once the body is pasted at the call site, the compiler can constant-fold, eliminate dead branches, and allocate registers across the old boundary. The key lesson: the machine already gets the performance benefit of inlining whether you refactor or not. When you apply Inline Method by hand, you are doing it for an entirely different audience โ€” human readers. Machine inlining removes call overhead; human inlining removes thinking overhead.

Before and After at a Glance

A delivery company rates its drivers. Read the "before" and notice the jump your eyes make.

// BEFORE: one useless hop
class Driver {
  private lateDeliveries = 0;
 
  rating(): number {
    return this.moreThanFiveLateDeliveries() ? 2 : 1;
  }
 
  private moreThanFiveLateDeliveries(): boolean {
    return this.lateDeliveries > 5;
  }
}

To understand rating(), you read the call, hop to moreThanFiveLateDeliveries, read one line, and hop back โ€” exactly the stairs-up-stairs-down route. The helper's name tells you nothing its body does not. Here is that wasted round trip, drawn as the conversation it really is:

Figure 4: The runtime round trip that pays no rent โ€” ask, receive one comparison, return

The whole message exchange exists to deliver one comparison that could have sat right there in rating(). So let us put it there:

// AFTER: the straight corridor
class Driver {
  private lateDeliveries = 0;
 
  rating(): number {
    return this.lateDeliveries > 5 ? 2 : 1;
  }
}

One method, one line, zero hops. Same behaviour, less furniture. The class diagram before and after shows what got demolished:

Figure 5: The Driver class loses a room nobody needed

Step-by-Step, the Safe Way ๐Ÿชœ

Let us inline a method slowly and safely, watching the code at every stage. Here is our starting point โ€” a school portal with a forwarding chain.

class LibraryService {
  canIssueBook(student: Student): boolean {
    return this.hasValidCard(student);
  }
 
  private hasValidCard(student: Student): boolean {
    return this.cardNotExpired(student);
  }
 
  private cardNotExpired(student: Student): boolean {
    return student.cardExpiry > new Date();
  }
}

Three methods to express one comparison! This is Corridor B, the science-lab stairs, and the staff-room stairs, all to reach a one-line library. Let us inline cardNotExpired first, then hasValidCard.

Step 1 โ€” Confirm the method is not polymorphic. Check: is cardNotExpired overridden in any subclass? If yes, stop โ€” inlining would erase the subclass behaviour, because callers would no longer dispatch dynamically. Our method is private and never overridden. Safe to continue.

Step 2 โ€” Find every caller. Use your IDE's "Find Usages" rather than your eyes, so no caller is missed. cardNotExpired is called from exactly one place: hasValidCard.

Step 3 โ€” Replace each call with the method's body. At the call site, substitute the body, renaming variables if any names clash with locals already present (none here).

class LibraryService {
  canIssueBook(student: Student): boolean {
    return this.hasValidCard(student);
  }
 
  private hasValidCard(student: Student): boolean {
    return student.cardExpiry > new Date();   // body pasted in
  }
 
  private cardNotExpired(student: Student): boolean {  // now unused
    return student.cardExpiry > new Date();
  }
}

Step 4 โ€” Compile and run the tests. The old method still exists, so nothing can be lost yet. Tests pass? Good.

Step 5 โ€” Delete the now-unused method.

class LibraryService {
  canIssueBook(student: Student): boolean {
    return this.hasValidCard(student);
  }
 
  private hasValidCard(student: Student): boolean {
    return student.cardExpiry > new Date();
  }
}

Test again. Green. One staircase removed. Now repeat the same five steps for hasValidCard. Its name is arguably a restatement of the expression too, and it has one caller. After the second round:

class LibraryService {
  canIssueBook(student: Student): boolean {
    return student.cardExpiry > new Date();
  }
}

From three methods to one, behaviour untouched. A reader now answers "when can a book be issued?" in one glance โ€” the thirty-second corridor, not the six-minute tour.

โš ๏ธ

Test after each call-site replacement, or at most after a small batch. If you replace ten call sites and then test, a failure could be in any of the ten. Also: never delete the method before all callers are replaced and tests are green โ€” the compiler finding a leftover caller is your friend, but a passing test suite is your judge.

One more care point for trickier bodies: if the method has multiple return statements, or recursion, or the body reassigns parameters, a simple paste will not work. Either rework the body first or leave it alone โ€” some methods are genuinely awkward to inline, and that is okay.

The full life of one inlining, as a state machine you can replay in your head:

Figure 6: The life of one inlining โ€” from suspicion to verified deletion

Notice the first transition. The polymorphism check comes before anything else. It is the one gate that can tell you to abandon the whole plan, so you pass through it first โ€” the same way you check a train is not running before you cross the tracks.

A Bigger Real-Life Example: The School Navigator App

Let us code Aman's corridor story properly. The school's app gives walking directions. A well-meaning senior developer wrapped every little step in its own method, "for flexibility." Here is what new readers face.

// BEFORE: the staircase route in code
class SchoolNavigator {
  directionsToLibrary(from: string): string {
    return this.libraryRoute(from);
  }
 
  private libraryRoute(from: string): string {
    return this.computeLibraryPath(from);
  }
 
  private computeLibraryPath(from: string): string {
    if (this.isMainGate(from)) {
      return this.mainGatePath();
    }
    return this.defaultPath(from);
  }
 
  private isMainGate(from: string): boolean {
    return from === "main-gate";
  }
 
  private mainGatePath(): string {
    return "Walk straight down Corridor A. Library is at the end.";
  }
 
  private defaultPath(from: string): string {
    return `From ${from}, reach Corridor A, then walk straight to the library.`;
  }
}

Seven methods. To trace one answer, a reader hops: directionsToLibrary โ†’ libraryRoute โ†’ computeLibraryPath โ†’ isMainGate โ†’ back โ†’ mainGatePath. Five hops to read five honest lines. Ask yourself the only question that matters for each method: does the name tell me more than the body?

MethodVerdictWhy
directionsToLibraryKeepPublic entry point; clear, useful name
libraryRouteInlinePure forwarding โ€” the "go upstairs" step
computeLibraryPathInlineForwarding plus a tiny if; restates its caller
isMainGateInlineRestates from === "main-gate" word for word
mainGatePathInlineReturns one string; the name adds nothing
defaultPathInlineReturns one string; the name adds nothing

We work one method at a time, innermost first, testing after each. Inline isMainGate:

private computeLibraryPath(from: string): string {
  if (from === "main-gate") {
    return this.mainGatePath();
  }
  return this.defaultPath(from);
}

Tests green. Inline mainGatePath and defaultPath:

private computeLibraryPath(from: string): string {
  if (from === "main-gate") {
    return "Walk straight down Corridor A. Library is at the end.";
  }
  return `From ${from}, reach Corridor A, then walk straight to the library.`;
}

Tests green. Now libraryRoute and computeLibraryPath are pure pass-throughs of each other. Collapse the chain into the public method:

// AFTER: the straight corridor
class SchoolNavigator {
  directionsToLibrary(from: string): string {
    if (from === "main-gate") {
      return "Walk straight down Corridor A. Library is at the end.";
    }
    return `From ${from}, reach Corridor A, then walk straight to the library.`;
  }
}

Seven methods became one. A reader who asks "how do directions work?" now reads five honest lines instead of spelunking through six hops. Nothing about the app's behaviour changed โ€” every test that passed before passes now. The difference for the reader is exactly Aman's difference:

Figure 7: Hops a reader makes to reach the real logic, before and after

And here is the seesaw in action: if this method later grows to forty lines with bus routes and rainy-day routes, we will happily use Extract Method to split it again โ€” but along lines that deserve names, like rainyDayRoute(). Inline first, see the whole picture, re-extract better. That is professional refactoring rhythm: Mrs. Iyer does not ban staircases; she bans staircases that lead back to where you started.

The before-and-after pattern compresses into one flowing picture:

Figure 8: Inline Method removes hops that added no meaning

The Same Refactoring in C# and Python

The identical move in C#, compressed.

// BEFORE
public class Driver
{
    private int _lateDeliveries;
 
    public int Rating()
    {
        return MoreThanFiveLateDeliveries() ? 2 : 1;
    }
 
    private bool MoreThanFiveLateDeliveries()
    {
        return _lateDeliveries > 5;
    }
}
 
// AFTER
public class Driver
{
    private int _lateDeliveries;
 
    public int Rating()
    {
        return _lateDeliveries > 5 ? 2 : 1;
    }
}

Check it is not overridden (it is private, so it cannot be), replace the single call with the body, delete the helper, run the tests. Done.

Python developers meet the same situation constantly with tiny wrapper functions:

# BEFORE: a wrapper that restates its body
def is_parcel(order):
    return order.type == "parcel"
 
def packing_charge(order):
    return 10 if is_parcel(order) else 0
 
# AFTER: the comparison sits where it is used
def packing_charge(order):
    return 10 if order.type == "parcel" else 0

One judgement call to practise here: if is_parcel were used in ten modules, inlining it would paste the string literal "parcel" in ten places โ€” a future bug factory. With one caller, inlining wins. With many callers, the wrapper suddenly earns its keep as the single home of that rule. Caller count changes the answer.

IDE Support โš™๏ธ

IDEs automate Inline Method beautifully: they find every caller for you, paste the body correctly, rename clashing variables, and offer to delete the original โ€” all in one action.

IDEHow to inlineShortcut
IntelliJ IDEA / Rider / other JetBrains IDEsPut the cursor on the method name โ†’ Refactor โ†’ Inline MethodCtrl+Alt+N (Cmd+Option+N on Mac)
ReSharper in Visual StudioCursor on method โ†’ Refactor This โ†’ Inline MethodCtrl+Shift+R then choose Inline
Visual Studio (built-in)Cursor on method โ†’ Quick Actions โ†’ "Inline method"Ctrl+. then choose Inline
VS CodeOpen the Refactor menu on the symbol; inline support varies by language extensionCtrl+Shift+R (Refactor) or Ctrl+.

JetBrains tools even ask whether to inline all usages and remove the method, or inline only the one usage under your cursor โ€” handy when you want to keep the method for other callers. And as always: the tool does the typing, but your test suite gives the verdict.

Inline or Keep? Making the Call ๐Ÿค”

Two questions decide almost every case: does the name add meaning beyond the body? and how many places call it? Put any suspicious method on this map before touching it:

Figure 9: Where a method lands decides whether to inline it

Bottom-left is Inline Method territory: the name echoes the body and only one caller would need the pasted code. Top-right is sacred ground: a meaningful name used everywhere, like isLeapYear โ€” inlining it would be vandalism. The other two corners need judgement: a meaningful name with one caller usually stays (names are cheap, clarity is precious), and an echo-name with many callers should make you pause โ€” maybe the name is the problem, and Rename Method is the better fix than Inline.

College corner: There is a classic software-engineering tension hiding here, and exam questions love it. Indirection is the foundation of abstraction โ€” "all problems in computer science can be solved by another level of indirection," as the saying goes (attributed to David Wheeler). But the lesser-known second half of the joke is: "...except the problem of too many levels of indirection." Every abstraction layer has a comprehension cost, and the layer must pay for that cost with meaning, reuse, or flexibility. Studies of code comprehension show that readers build mental call graphs as they read; each unnecessary hop enlarges that graph and burdens working memory without adding information. Inline Method is the discipline of auditing layers and deleting the ones that fail the cost-benefit test. Good engineers are not the ones who add the most abstraction โ€” they are the ones who keep only the abstraction that pays rent.

Benefits and Risks โš–๏ธ

Point
โœ…Removes a layer of indirection the reader had to climb through โ€” the logic appears where it is used.
โœ…Deletes a name that no longer earns its keep, reducing noise in the class.
โœ…Collapses badly-cut helpers into one place so they can be re-extracted along better seams.
โœ…The standard tool for dismantling a Middle Man that only forwards calls.
โš ๏ธNever inline a polymorphic method. If subclasses override it, the override is the whole point; inlining erases that behaviour.
โš ๏ธInlining a method with many callers copies its body everywhere โ€” duplicate code, the opposite of DRY. Inline only when callers are few, or when you plan to re-extract immediately.
โš ๏ธA short method with a genuinely meaningful name is documentation. isLeapYear(y) should stay even though its body is one line. Do not inline merely because something is brief.
โš ๏ธRecursive methods and bodies with multiple returns are awkward or unsafe to inline mechanically.

The seesaw with Extract Method. Inline Method and Extract Method are exact inverses โ€” one removes an abstraction boundary, the other introduces one. Think of a seesaw: too many tiny hop-methods, push down on the Inline side; one bloated giant, push down on the Extract side. The goal is never "more methods" or "fewer methods." The goal is that every name in the code pays for itself in clarity. Aman's school needs both corridors and staircases โ€” it just does not need staircases that go up and come down to the same floor.

Which Smells Does It Cure?

SmellHow Inline Method helps
Middle ManInline the forwarding methods so callers reach the real object directly.
Speculative GeneralityWrappers added "just in case" are removed once the case never arrives.
Over-extraction (too many trivial helpers)Folds noise methods back in; often followed by a smarter re-extraction.
Long Method (indirectly)Inlining first lets you see the whole picture, so the next Extract Method cuts at better places.

Quick Revision Box

+--------------------------------------------------------------+
|                INLINE METHOD โ€” QUICK REVISION                |
+--------------------------------------------------------------+
| WHAT   : Replace calls to a trivial method with its body,    |
|          then delete the method.                             |
| 2ND ED : Fowler now calls it "Inline Function".              |
| WHEN   : Name restates the body, chains of forwarders,       |
|          Middle Man, before a smarter re-extraction.         |
| STEPS  : 1. Confirm NOT overridden anywhere (polymorphism)   |
|          2. Find ALL callers with the IDE                    |
|          3. Replace each call with the body                  |
|          4. TEST after each replacement                      |
|          5. Delete the unused method, test again             |
| NEVER  : Polymorphic methods, many-caller methods,           |
|          well-named methods hiding real complexity.          |
| INVERSE: Extract Method (the other end of the seesaw)        |
+--------------------------------------------------------------+

Practice Exercise ๐Ÿ

Your turn! This CanteenService is full of corridor shortcuts. Decide which methods deserve to live and which should be inlined. Aim to finish with the smallest set of methods where every remaining name adds real meaning.

class CanteenService {
  billFor(order: Order): number {
    return this.computeBill(order);
  }
 
  private computeBill(order: Order): number {
    return this.itemsTotal(order) + this.packingCharge(order);
  }
 
  private itemsTotal(order: Order): number {
    return order.items.reduce((sum, i) => sum + i.price, 0);
  }
 
  private packingCharge(order: Order): number {
    return this.isParcel(order) ? 10 : 0;
  }
 
  private isParcel(order: Order): boolean {
    return order.type === "parcel";
  }
}

Hints: computeBill is a pure forwarder โ€” inline it. isParcel restates one comparison with one caller โ€” inline it. But think twice about itemsTotal: its name compresses a reduce expression that is not instantly obvious, so many developers would keep it โ€” that one is on the right side of the quadrant map. There is no single perfect answer; the skill you are practising is judging which names earn their keep. Inline one method at a time, run the tests after each, and say out loud: "tests pass, behaviour unchanged." Do that, and you have learnt what took Aman two staircases and one kind librarian to learn: the shortest route is the one with no useless hops. Well done!

Frequently asked questions

What is the Inline Method refactoring in simple words?
Inline Method is the opposite of Extract Method. When a method's body is just as clear as its name, you replace every call to the method with the body itself, then delete the method. One useless layer disappears.
When should I NOT inline a method?
Do not inline a method that is overridden in subclasses (polymorphic), because the dynamic dispatch is the whole point. Also avoid inlining a method called from many places, since that copies its body everywhere, and keep short methods whose good name explains genuinely tricky logic.
What did Fowler rename Inline Method to in the 2nd edition?
In Refactoring, 2nd edition (2018), Martin Fowler renamed Inline Method to Inline Function, matching the rename of Extract Method to Extract Function. Both names describe the same refactoring.
Why would anyone remove a method? Aren't small methods good?
Small methods are good only when their names add meaning. A method like moreThanFiveLateDeliveries() that wraps lateDeliveries > 5 adds a hop without adding understanding. Removing such noise makes the real logic easier to see.
Is Inline Method sometimes just a preparation step?
Yes, very often. When a function is split into badly chosen helpers, developers first inline everything back into one place, look at the full picture, and then re-extract along better lines using Extract Method.

Further reading

Related Lessons