Skip to main content
CleanCodeMastery

Replace Temp with Query: Ask Fresh, Don't Trust Yesterday's Chit

Learn Replace Temp with Query with a canteen story, TypeScript and C# examples, and safe steps. Turn local variables into reusable methods, one source of truth.

24 min read Updated June 11, 2026beginner
refactoringreplace temp with queryquery methodtemporary variablesclean codecomposing methods

๐ŸฅŸ The Story of the Samosa Price Chit

Raju of class 9B loves the school canteen's samosas. The canteen is run by Shankar uncle, who writes the day's prices on a board above the counter. Every morning, before class, Raju walks to the canteen, reads the board, and copies the price onto a little chit of paper: "Samosa = Rs 12." He keeps the chit in his pocket. Whenever a friend asks, "Raju, how much is a samosa today?", he proudly pulls out his chit and reads it.

Sounds organised, no? Raju even feels efficient โ€” "Why should I walk to the canteen every time? I already noted it down!" But watch what goes wrong.

At lunch time, Shankar uncle gets a fresh stock of potatoes at a cheaper rate and changes the board: "Samosa = Rs 10." Raju's chit still says 12. Three friends pay Raju 12 rupees each for samosa orders, and Raju overpays at the counter โ€” confusion everywhere. The chit has become stale. It was true when it was written; it is false now; and nothing about the chit looks different. That is the most dangerous property of a stale copy: it carries no expiry stamp.

It gets worse. Raju's friends start making their own chits by copying his. Priya copies it at 10 a.m., Aman copies Priya's at 11, and by lunch there are five chits floating around 9B, each written at a different time, each showing whatever the board said at that moment. Whose chit is correct? Nobody knows. Five copies, five versions of the truth, zero ways to tell them apart.

Figure 1: One ordinary school day is enough for a copied price to drift away from the truth.

What is the simple fix? Throw away the chits and ask the canteen directly, every time. The board above Shankar uncle's counter is the one source of truth. Asking takes two extra seconds, but the answer is always fresh, always correct, and everybody who asks gets the same answer at the same moment.

Figure 2: The four-step lifecycle of every stale-copy bug ever written.

In code, a temporary variable is Raju's chit. It copies a calculated value at one moment, inside one method, and holds it there. Other methods cannot see it, so they make their own copies of the formula โ€” Priya and Aman with their own chits. Today's refactoring replaces the chit with a question you can ask any time: it is called Replace Temp with Query.

๐Ÿค” What is Replace Temp with Query?

Here is the idea in one breath:

You store the result of an expression in a temporary variable. Move the expression into its own method (a query), and call that method wherever the temp was used.

A query is a method that answers a question โ€” it calculates and returns a value, and it changes nothing. No saving, no printing, no updating. Because it changes nothing, it is always safe to call, from anywhere, as many times as needed.

A tiny example. Before:

class Order {
  // ...
  price(): number {
    const basePrice = this.quantity * this.itemPrice;
    if (basePrice > 1000) {
      return basePrice * 0.95;
    }
    return basePrice * 0.98;
  }
}

After:

class Order {
  // ...
  price(): number {
    if (this.basePrice() > 1000) {
      return this.basePrice() * 0.95;
    }
    return this.basePrice() * 0.98;
  }
 
  private basePrice(): number {
    return this.quantity * this.itemPrice;
  }
}

The local variable basePrice is gone. In its place stands a small named method basePrice(). The difference looks tiny but is actually huge: a local variable can be seen by exactly one method; a query can be answered for the whole class. Tomorrow, when invoiceLine() or loyaltyPoints() needs the base price, it simply asks โ€” no copy of the formula, no chit.

Figure 3: Calling code never stores yesterday's answer โ€” it asks the query and always receives the latest value.
๐Ÿ’ก

Remember the canteen rule: a local variable is a chit in one pocket; a query is the price board on the wall. The chit goes stale and gets copied wrongly. The board is one source of truth that everyone reads fresh.

Martin Fowler describes this refactoring in Refactoring (2nd edition, page 178), and both his catalog and Refactoring Guru list it among the most important "composing methods" techniques โ€” because it is the move that unlocks the decomposition of long methods, as we will see.

College corner: single source of truth and staleness. This little school story is secretly one of the deepest ideas in computer science. A cached copy of data is only correct until the original changes; after that, every reader of the copy gets a silently wrong answer. You will meet this same monster wearing many costumes: CPU caches that must be kept coherent across cores, web caches that serve yesterday's page, DNS records that take hours to expire, denormalised database columns that disagree with the tables they summarise, and React state that someone copied out of props and forgot to update. Phil Karlton's famous joke โ€” "there are only two hard things in computer science: cache invalidation and naming things" โ€” is funny precisely because it is true. Replace Temp with Query is the smallest possible victory over that monster: instead of inventing an invalidation protocol for the copy, you delete the copy and recompute on demand. Whenever recomputation is affordable, no-copy beats managed-copy, because a copy that does not exist can never be stale. Database students will recognise this as the same instinct behind normalisation: store each fact once, derive everything else.

๐Ÿ” When do we need it?

Watch for these situations:

  1. The same formula appears in two or more methods. That is Duplicate Code being born. Each copy is a chit, and one day the copies will disagree. A single query ends the copying.
  2. You are trying to shorten a Long Method and the temps are in the way. This is the big one. A method stuffed with locals resists Extract Method, because every piece you want to pull out reads and writes those locals โ€” you would have to pass them as parameters and return them back, which is ugly. Turn the temps into queries first, and the pieces suddenly depend only on the object. Extraction becomes easy.
  3. A calculated value deserves a name visible to the whole class. basePrice, discountFactor, gstAmount โ€” these are concepts of the object, not private scribbles of one method.
  4. A subclass might need to calculate the value differently. A local variable cannot be overridden. A query method can. Polymorphism only works on methods.

Not every temp in a method is a candidate, of course. In a typical class that has grown for a year, the temps sort roughly like this:

Figure 4: Not all temps are equal โ€” the shareable, recomputable ones are this refactoring's customers.

And one pre-condition to check before starting: the temp must be assigned exactly once, and its expression must be side-effect free. If the variable is assigned twice for two different jobs, first apply Split Temporary Variable. If the temp is trivial and used once, the lighter Inline Temp may be all you need.

โš–๏ธ Before and after at a glance

Here is the shape of the change, slightly bigger this time, in TypeScript:

Before:

class Bill {
  constructor(private items: { price: number; qty: number }[]) {}
 
  total(): number {
    const subtotal = this.items.reduce((s, i) => s + i.price * i.qty, 0);
    const gst = subtotal * 0.05;
    return subtotal + gst;
  }
}

After:

class Bill {
  constructor(private items: { price: number; qty: number }[]) {}
 
  total(): number {
    return this.subtotal() + this.gst();
  }
 
  private subtotal(): number {
    return this.items.reduce((s, i) => s + i.price * i.qty, 0);
  }
 
  private gst(): number {
    return this.subtotal() * 0.05;
  }
}

Read the after version aloud: "total is subtotal plus GST." It reads like a sentence from the textbook. Each calculation has a name, each name is callable from anywhere in Bill, and notice the bonus โ€” gst() happily calls subtotal(). Queries can build on queries, like one price board referring to another.

Figure 5: Before, values live as chits inside one method. After, every method reads the same price board.

The class itself, after refactoring, advertises its concepts in its method list. A teammate skimming the class outline learns the vocabulary of billing without reading a single formula:

Figure 6: After the refactoring, the class outline reads like a glossary of the billing domain.

๐Ÿชœ Step-by-step, the safe way

We refactor in baby steps, testing after each one. Let us walk the official mechanics on this code:

class Bill {
  total(): number {
    const subtotal = this.items.reduce((s, i) => s + i.price * i.qty, 0);
    const gst = subtotal * 0.05;
    return subtotal + gst;
  }
}

Step 1: Check the temp is assigned once and the expression is pure. subtotal is assigned once. Its expression only reads this.items and adds numbers โ€” no side effects, same answer every call. Both checks pass. (If a temp were assigned twice, we would stop and apply Split Temporary Variable first.)

Step 2: Make the temp read-only and run the tests. In TypeScript we use const; in Java, final; this proves single assignment. Ours is already const. Tests green.

Step 3: Extract the right-hand side into a query method. Do not delete the temp yet! This is the safe intermediate state โ€” the temp now simply calls the new query:

class Bill {
  total(): number {
    const subtotal = this.subtotal();   // temp still here, now fed by the query
    const gst = subtotal * 0.05;
    return subtotal + gst;
  }
 
  private subtotal(): number {
    return this.items.reduce((s, i) => s + i.price * i.qty, 0);
  }
}

Step 4: Run the tests. Green? The query is correct.

Step 5: Inline the temp. This is exactly the Inline Temp refactoring from the previous lesson โ€” see how the small tools combine:

class Bill {
  total(): number {
    const gst = this.subtotal() * 0.05;
    return this.subtotal() + gst;
  }
 
  private subtotal(): number {
    return this.items.reduce((s, i) => s + i.price * i.qty, 0);
  }
}

Tests again. Green.

Step 6: Repeat for the next temp. Extract gst:

class Bill {
  total(): number {
    return this.subtotal() + this.gst();
  }
 
  private subtotal(): number {
    return this.items.reduce((s, i) => s + i.price * i.qty, 0);
  }
 
  private gst(): number {
    return this.subtotal() * 0.05;
  }
}

Final test run. Done. Notice the order we worked in: one temp at a time, tests between every move, and a later query (gst) ended up calling an earlier one (subtotal) โ€” that is normal and good.

Seen from a distance, every temp you treat walks the same path from pocket chit to price board:

Figure 7: The journey of one value โ€” from cached temp, through staleness risk, to an always-fresh query.
โš ๏ธ

Two safety rules are non-negotiable. First, run the tests after every single step, not at the end โ€” if step 3 silently changed behaviour, you want to know before stacking step 5 on top. Second, never make a query out of an expression with side effects. If the expression dequeues an item, increments a counter, or reads user input, calling it twice gives a different world than calling it once. Queries must be safe to ask any number of times.

College corner: command-query separation. The word "query" here is borrowed from Bertrand Meyer's principle of command-query separation (CQS): every method should either be a command that changes state and returns nothing, or a query that returns a value and changes nothing โ€” never both. Queries in the CQS sense have a property that functional programmers call referential transparency: a call can be replaced by its result (or repeated any number of times) without changing the meaning of the program. That property is exactly why this refactoring is safe. It is also why the side-effect pre-condition is non-negotiable: a method that both answers and acts โ€” like nextToken() on a parser โ€” is not referentially transparent, and turning a temp into such a method would change behaviour in ways no test suite enjoys discovering. When you later meet CQRS in distributed-systems courses, you will recognise the same idea scaled up to whole services.

๐Ÿซ A bigger real-life example

Back to Raju's canteen โ€” but now the school has a small canteen app, and the code has Raju's chit problem exactly. Look closely at the two methods:

interface MenuItem {
  name: string;
  price: number;
  qty: number;
}
 
class CanteenOrder {
  constructor(private items: MenuItem[], private isStaff: boolean) {}
 
  totalBill(): number {
    const subtotal = this.items.reduce((s, i) => s + i.price * i.qty, 0);
    const discount = this.isStaff && subtotal > 100 ? subtotal * 0.1 : 0;
    const packingCharge = this.items.length > 3 ? 10 : 5;
    return subtotal - discount + packingCharge;
  }
 
  receiptText(): string {
    // the same formulas, copied โ€” Raju's friends making their own chits!
    const subtotal = this.items.reduce((s, i) => s + i.price * i.qty, 0);
    const discount = this.isStaff && subtotal > 100 ? subtotal * 0.1 : 0;
    return (
      `Canteen Receipt\n` +
      `Subtotal: Rs ${subtotal}\n` +
      `Discount: Rs ${discount}\n` +
      `Pay: Rs ${this.totalBill()}`
    );
  }
}

See the disease? receiptText() could not reach the locals inside totalBill(), so somebody copied the formulas. Today both copies agree. But suppose next month Shankar uncle changes the staff discount rule to 15%, and the developer updates totalBill() but forgets receiptText(). The bill says one number, the printed receipt says another, and Shankar uncle spends his evening recounting coins, muttering about computers. That is the stale chit, in production.

Now we apply Replace Temp with Query to every temp, one at a time, tests after each step. The destination:

class CanteenOrder {
  constructor(private items: MenuItem[], private isStaff: boolean) {}
 
  totalBill(): number {
    return this.subtotal() - this.discount() + this.packingCharge();
  }
 
  receiptText(): string {
    return (
      `Canteen Receipt\n` +
      `Subtotal: Rs ${this.subtotal()}\n` +
      `Discount: Rs ${this.discount()}\n` +
      `Pay: Rs ${this.totalBill()}`
    );
  }
 
  private subtotal(): number {
    return this.items.reduce((s, i) => s + i.price * i.qty, 0);
  }
 
  private discount(): number {
    return this.isStaff && this.subtotal() > 100 ? this.subtotal() * 0.1 : 0;
  }
 
  private packingCharge(): number {
    return this.items.length > 3 ? 10 : 5;
  }
}

Count the wins:

  • The duplicated formulas are gone. The discount rule now lives in exactly one place. Change it once, and bill and receipt agree forever โ€” one price board, no chits.
  • totalBill() shrank from four tangled lines into one readable sentence: subtotal minus discount plus packing charge.
  • Each concept โ€” subtotal, discount, packing charge โ€” now has a name that the whole class (and your teammates) can discover and reuse.
  • Tomorrow, if monthlyReport() needs the discount, it asks. No copying.
Figure 8: After the refactoring, every consumer asks the same queries โ€” one source of truth.

Teams that adopt this habit see the difference in their bug trackers, not just in their taste. Stale-copy bugs โ€” "report disagrees with bill", "summary shows old total" โ€” fall off a cliff once derived values are computed in exactly one place:

Figure 9: A team's rough bug tally before and after moving shared calculations into queries.

๐ŸŸฆ The same refactoring in C#

The same medicine works in C#. Here is a delivery-order class before treatment:

public class DeliveryOrder
{
    private readonly List<OrderItem> _items;
    private readonly double _distanceKm;
 
    public DeliveryOrder(List<OrderItem> items, double distanceKm)
    {
        _items = items;
        _distanceKm = distanceKm;
    }
 
    public decimal TotalPayable()
    {
        decimal itemsTotal = _items.Sum(i => i.Price * i.Quantity);
        decimal deliveryFee = itemsTotal > 500m ? 0m : (decimal)_distanceKm * 6m;
        return itemsTotal + deliveryFee;
    }
}

And after extracting each temp into a query:

public class DeliveryOrder
{
    private readonly List<OrderItem> _items;
    private readonly double _distanceKm;
 
    public DeliveryOrder(List<OrderItem> items, double distanceKm)
    {
        _items = items;
        _distanceKm = distanceKm;
    }
 
    public decimal TotalPayable() => ItemsTotal() + DeliveryFee();
 
    private decimal ItemsTotal() => _items.Sum(i => i.Price * i.Quantity);
 
    private decimal DeliveryFee() =>
        ItemsTotal() > 500m ? 0m : (decimal)_distanceKm * 6m;
}

Two C#-flavoured notes. First, expression-bodied members (=>) make small queries beautifully short โ€” perfect partners for this refactoring. Second, C# offers a middle option: a read-only property like private decimal ItemsTotal => ...;. Properties are idiomatic C# for cheap, side-effect-free values. The guideline most .NET teams follow: use a property when the calculation is cheap and feels like data; use a method when it does noticeable work. Either way, the refactoring idea is identical โ€” the chit becomes a question.

๐Ÿ› ๏ธ IDE support

Here is an honest fact: most IDEs do not have a single button labelled "Replace Temp with Query". But they do not need one, because the refactoring is just two button-presses they do have: Extract Method on the temp's expression, then Inline Variable on the temp.

ToolHow to do it
JetBrains Rider / IntelliJ IDEASelect the expression โ†’ Ctrl+Alt+M (Extract Method), then cursor on the temp โ†’ Ctrl+Alt+N (Inline Variable)
Visual StudioSelect the expression โ†’ Ctrl+. โ†’ Extract method, then cursor on temp โ†’ Ctrl+. โ†’ Inline temporary variable
VS CodeSelect expression โ†’ Ctrl+Shift+R โ†’ Extract to method/function, then inline support depends on the language extension
Eclipse (Java)Alt+Shift+M (Extract Method), then Alt+Shift+I (Inline)

Because both halves are automated, the whole refactoring is nearly risk-free in a modern IDE: the tool finds every reference, fixes the calls, and refuses to proceed if the move would change behaviour. The keyboard sequence "extract, test, inline, test" should become muscle memory.

๐Ÿ“Š Benefits and risks

Every trade has two sides. Here is the full balance sheet โ€” including the performance question that everybody asks:

BenefitsRisks / Costs
The calculation gets a name visible to the whole class โ€” one source of truthThe query recomputes on each call; a cached temp computed once
Kills duplicate formulas before they can disagreeIf the expression is genuinely expensive and called in a hot loop, recomputation can cost real time
Removes temps, the main obstacle to Extract Method โ€” long methods become splittableMore small methods in the class; readers must trust names instead of seeing formulas inline
Subclasses can override a query to change the calculation โ€” impossible for a localOnly works for side-effect-free expressions; stateful temps cannot become queries
Methods read as sentences built from named questionsA temp assigned twice needs Split Temporary Variable first โ€” extra steps

Let us be honest about the performance row, because hand-waving helps nobody. Yes, gst() calling subtotal() means the items get summed twice where the old code summed once. That is a real, measurable fact. Now the context: for a bill with even a thousand items, the second sum costs microseconds โ€” far below anything a user or even a profiler would normally notice โ€” and modern compilers and JIT runtimes often optimise such calls aggressively. Fowler's guidance, repeated in both editions, is to take the clarity first and treat performance as a separate, later concern: refactor for clarity, then profile, and only if the profiler points at a specific query do you deliberately add caching back (for example, a lazily computed field). Programmers who skip this refactoring "for performance" usually pay a permanent clarity tax to avoid a cost that never existed.

The decision is really a two-axis judgment โ€” how costly is the computation, and how likely is the underlying data to change? Plot your temp and read off the answer:

Figure 10: The honest performance picture โ€” only the costly-and-stable corner ever argues for keeping a cached temp.

College corner: memoization, the disciplined cache. Suppose the profiler really does point at a query โ€” say an expensive report total called thousands of times. The grown-up fix is memoization: the query keeps its public face, but privately stores its first answer and returns it on later calls, invalidating the stored value whenever the underlying data changes. Notice what this is: a cache, but inside the single source of truth, where one owner controls both the value and its invalidation. That is wildly safer than the original temp, where every method kept its own pocket chit with no invalidation at all. Python hands you this for free with functools.cached_property:

from functools import cached_property
 
class CanteenOrder:
    def __init__(self, items, is_staff):
        self._items = items
        self._is_staff = is_staff
 
    @cached_property
    def subtotal(self):          # computed once, then served from cache
        return sum(i.price * i.qty for i in self._items)
 
    @property
    def discount(self):
        if self._is_staff and self.subtotal > 100:
            return self.subtotal * 0.1
        return 0

The lesson order matters, though: first make it a query (correct and clear), then memoize if measured (fast). Never the reverse.

๐Ÿงน Which smells does it cure?

This refactoring is one of the strongest smell-killers in the catalog, because it attacks two big smells at once:

SmellHow Replace Temp with Query helps
Long MethodRemoves the local variables that make a long method impossible to split; afterwards Extract Method can break it into clean pieces
Duplicate CodeA single named query replaces every copied formula โ€” the chits are gone, only the price board remains
CommentsA query named discount() explains itself; the comment "// calculate staff discount" becomes unnecessary

And remember its position in the little family of temp-variable refactorings: Inline Temp for trivial one-use temps, Split Temporary Variable when one temp does two jobs, Extract Variable when an expression needs a name only locally, and Replace Temp with Query when the name deserves class-wide visibility. Four tools, one shared question: where should this value's name live?

Figure 11: Choosing the right tool for a temporary variable.

The whole lesson, folded into one picture for revision time:

Figure 12: Replace Temp with Query at a glance โ€” the story, the rules, and the honest fine print.

๐Ÿ“ฆ Quick revision box

+--------------------------------------------------------------------+
|              REPLACE TEMP WITH QUERY - REVISION CARD               |
+--------------------------------------------------------------------+
| WHAT     : Move a temp's expression into a method (query);        |
|            call the method wherever the temp was used             |
| STORY    : Stop writing price chits; ask the canteen each time    |
| WHY      : Local variable = visible to ONE method                 |
|            Query method  = visible to the WHOLE class             |
| UNLOCKS  : Extract Method (temps were blocking the split)         |
| STEPS    : 1. Temp assigned once? expression pure?                |
|            2. Make it const/final, test                           |
|            3. Extract expression -> query method, test            |
|            4. Inline the temp, test                               |
|            5. Repeat for next temp                                |
| HONESTY  : Query recomputes each call. Usually harmless.          |
|            Profile first; cache later ONLY if proven hot.         |
| DO NOT   : Make queries from side-effecting expressions           |
| FAMILY   : Inline Temp (trivial), Split Temp Variable (two jobs), |
|            Extract Variable (local name only)                     |
+--------------------------------------------------------------------+

๐Ÿ‹๏ธ Practice exercise

Your turn at the counter. Below is a hostel mess-bill class with chits everywhere. Refactor it.

interface Meal {
  type: "breakfast" | "lunch" | "dinner";
  price: number;
}
 
class MessBill {
  constructor(
    private meals: Meal[],
    private daysPresent: number,
    private isScholarshipStudent: boolean
  ) {}
 
  monthlyBill(): number {
    const mealTotal = this.meals.reduce((s, m) => s + m.price, 0);
    const fixedCharge = this.daysPresent > 20 ? 300 : 150;
    const concession = this.isScholarshipStudent ? mealTotal * 0.25 : 0;
    return mealTotal + fixedCharge - concession;
  }
 
  billSummary(): string {
    const mealTotal = this.meals.reduce((s, m) => s + m.price, 0);
    const concession = this.isScholarshipStudent ? mealTotal * 0.25 : 0;
    return `Meals: Rs ${mealTotal}, Concession: Rs ${concession}, Pay: Rs ${this.monthlyBill()}`;
  }
}

Your tasks:

  1. List every temporary variable and check the two pre-conditions for each: assigned once? expression side-effect free?
  2. Identify the duplicated formulas between monthlyBill() and billSummary(). Which smell is this, and what bug could it cause when the concession rule changes? Tell it as a one-paragraph Raju story: who wrote the chit, who copied it, and who pays wrong at the counter.
  3. Apply Replace Temp with Query to mealTotal, fixedCharge, and concession, strictly one temp at a time: extract the query, test, inline the temp, test. Write the intermediate version after the first extraction, before inlining โ€” that snapshot is the proof you followed the mechanics.
  4. After refactoring, concession() should call mealTotal(). Explain in one line why a query calling another query is perfectly fine.
  5. Place mealTotal on the quadrant chart from Figure 10: 10,000 meals in the list, monthlyBill() called once per student per month. Is the recomputation cost worth worrying about? Use the "profile first" rule to justify your answer.
  6. Bonus for college students: write a memoized version of mealTotal() with a private cached field, and then write one sentence on what event would force you to invalidate that cache. Feel the weight of that sentence โ€” that weight is why we prefer plain queries until a profiler complains.

When every important calculation in your class is a named question instead of a pocket chit, you have mastered this refactoring. Raju asks the board now; his friends get the right price every time. Next lesson: what to do when one poor variable is forced to do two different jobs โ€” Split Temporary Variable.

Frequently asked questions

What exactly is a 'query' in this refactoring's name?
A query is a method that calculates and returns a value without changing anything. It only answers a question, like asking the canteen 'what is the samosa price?'. Because it changes nothing, you can call it any number of times, from anywhere in the class, with no fear.
Won't calling a method again and again be slower than a variable?
A little, sometimes. The query recomputes what the temp had cached. For typical calculations this cost is too small to measure, and compilers often optimise it away. Fowler's advice: take the clarity first, and only add caching back if a profiler proves a real hot spot.
Can every temporary variable become a query?
No. The expression must be side-effect free and must give the same answer each call. A temp that stores the result of a one-time action, like removing an item from a queue, cannot be re-asked. And a temp assigned twice for two purposes must first go through Split Temporary Variable.
How is this different from Extract Variable?
Extract Variable creates a named local that only one method can see. Replace Temp with Query creates a named method the whole class can call. Use a variable when the value matters only inside one method; use a query when other methods need the same calculation.
Why do people call this the key that unlocks Extract Method?
Long methods are hard to split because their pieces share local variables, which would need to be passed around as parameters. Once temps become queries, the pieces depend only on the object, not on locals. Then Extract Method becomes easy, almost automatic.

Further reading

Related Lessons