What is the "Replace Temp with Query" refactoring, what does it unlock, and when is it the wrong move?
answer
- Temp → side-effect-free function (a query)
- Make it final first; compiler proves single assignment
- Preparatory move: unblocks Extract Function
- Locals force parameters and multi-returns
- Wrong when: side effects, frozen value, hot-path cost
basics
~20 sReplace a local variable holding the result of an expression with a small function that computes that expression, then call the function wherever the variable was used. Fewer locals makes the surrounding code easier to extract into other functions.
solid answer
~60 sReplace Temp with Query turns a temporary variable into a method. If a local is assigned once from an expression, extract that expression into a function (a *query* — a call that returns a value and changes nothing) and replace every read of the temp with a call. Mechanics: confirm the temp is assigned exactly once and the expression is side-effect-free; make the temp read-only/final and run tests to prove the single assignment; extract the expression into a well-named function; inline the temp. The pay-off is indirect but large: locals are the main obstacle to Extract Function, because every temp the fragment reads becomes a parameter and every temp it writes must be returned. Removing temps shrinks those sets, so bigger, cleaner extractions become possible — it is usually a preparatory step before Extract Function or Extract Class. It is the wrong move when the expression has side effects, when the value must be captured at a point in time because the underlying state mutates, or when recomputation is genuinely expensive on a hot path.
code
pseudocode · 11 lines// before: temps block extraction (any extracted fragment would need them as params)
function price(order) {
const basePrice = order.quantity * order.itemPrice
const discount = max(basePrice - 500, 0) * 0.05
return basePrice - discount + min(basePrice * 0.1, 100)
}
// after: temps became queries; the body reads as intent and each piece is reusable
function price(order) { return basePrice(order) - discount(order) + shipping(order) }
function basePrice(order) { return order.quantity * order.itemPrice }
function discount(order) { return max(basePrice(order) - 500, 0) * 0.05 }go deeper
Define it concretely: replace a local variable with a small function that computes the same value, and call the function where the variable was used.
Add the mechanics (single assignment, no side effects, make it final to let the compiler verify) and the real motivation — fewer locals means Extract Function needs fewer parameters and returns.
Lead with the preparatory-refactoring framing, enumerate the counter-indications (side effects, snapshot semantics, hot-path cost), and discuss the tension with Extract Variable as a deliberate directional choice.
Connect it to Command–Query Separation and to the cost model of the context (in-process call vs database/network round trip), and to how preparatory refactorings are sequenced and reviewed separately from behaviour changes.
### Vocabulary - A **temp** (temporary variable / local) is a variable declared inside a function to hold an intermediate value. - A **query** is a function that **returns a value and has no observable side effects** — you can call it any number of times, in any order, and nothing changes. The distinction between queries and **modifiers** (functions that change state) is the *Command–Query Separation* principle, and it is the precondition that makes this refactoring safe. ### The refactoring **Before** ``` basePrice = quantity * itemPrice if (basePrice > 1000) return basePrice * 0.95 else return basePrice * 0.98 ``` **After** ``` function basePrice() { return quantity * itemPrice } if (basePrice() > 1000) return basePrice() * 0.95 else return basePrice() * 0.98 ``` **Mechanics, step by step:** 1. Check the temp is assigned **exactly once** and that the assigning expression is **side-effect-free**. If it is assigned in several places, either split it into separate temps first (*Split Variable*, since one variable serving two purposes is its own smell) or abandon the move. 2. Declare the temp read-only (`final`/`const`/`val`) and compile — the compiler now proves the single-assignment claim for you. This is a classic trick: let the language check the precondition instead of eyeballing it. 3. Extract the right-hand-side expression into a new function, named for the concept (`basePrice`, `subtotal`, `discountRate`). 4. Have the temp be assigned from the new function; run tests. 5. Inline the temp: replace each use with the call; delete the declaration; run tests. ### Why bother — the real motivation The readability gain is modest. The structural gain is the point. When you attempt **Extract Function**, every local the fragment *reads* becomes a parameter and every local it *writes* and that is still live afterwards must be returned. A function stuffed with temps therefore resists extraction: you either get six-parameter extractions or extractions that need to return three values. Converting temps to queries removes them from both sets, so the fragment becomes extractable with few or no parameters. This is a **preparatory refactoring**: you make an awkward change easy, then make the easy change. A second benefit: the extracted query is available to *other* functions in the class, which frequently exposes duplication — the same subtotal calculation was open-coded in three methods. ### When it is the wrong move 1. **The expression has side effects.** If computing it writes a field, logs, increments a counter, or performs I/O, turning one evaluation into N evaluations changes behaviour. Fix the side effect first (*Separate Query from Modifier*) or leave the temp. 2. **Time-sensitive capture.** If the expression reads mutable state that changes between the original assignment and the later uses — a clock, a mutable collection being modified in a loop, a field another thread can write — the temp deliberately **freezes** a value. Replacing it with a query re-reads live state and silently changes results. This is the most dangerous failure mode because tests with static fixtures will not catch it. 3. **Expensive recomputation on a hot path.** The temp may be a deliberate cache. Usually the cost is negligible and clarity wins — but if profiling says otherwise, keep the temp, or make the query memoised (accepting that memoisation adds mutable state and invalidation concerns). 4. **Loop accumulators.** A variable accumulating inside a loop is assigned many times and is not a candidate; the corresponding move is *Replace Loop with Pipeline* or extracting the whole loop. 5. **The expression depends on parameters or locals unavailable to the new function.** Then the query needs parameters, which is fine, but if it needs four, the seam is wrong. ### Related catalog entries - **Inline Temp** — the degenerate case: a temp assigned once from a simple expression and used once; just inline it. - **Split Variable** — one variable reused for two different meanings; give each its own name before doing anything else. - **Extract Variable** (the *inverse* direction) — introduce a named temp to explain a hairy expression. Note the catalog contains refactorings that oppose each other; which one applies depends on whether the value is better explained by a name local to this function or by a reusable query on the object. Neither direction is universally right, and being able to say *why* you would go one way here and the other way there is the senior-level answer. - **Encapsulate Variable** — for fields rather than locals. ### Language-agnostic note The idea holds anywhere functions exist: object methods, module-level functions, closures, even SQL views (a view is literally "replace temp table with query"). What varies is the cost model — in a language with cheap calls and an optimiser, replacing a temp with a query is usually free; in a context where each call crosses a network or a database, it obviously is not, and that judgement, not the mechanics, is what interviewers probe.
- Why make the temp read-only/final before extracting anything?Because the refactoring's precondition is that the temp is assigned exactly once. Declaring it final turns that assumption into a compile-time check: if there is a second assignment anywhere, the code no longer compiles and you learn it in seconds instead of shipping a bug.
- Extract Variable is essentially the inverse of this refactoring. When do you go that direction instead?When the value's meaning is local to this one function and does not deserve a place in the object's interface, or when the expression depends on parameters that the object cannot see. Extract Variable names a step in a local computation; Replace Temp with Query promotes it to reusable behaviour of the object.
- Your temp holds the current timestamp and is used in four places. Is this a candidate?No — the temp exists precisely to freeze one instant. Turning it into a query would read the clock four times, so the four uses could disagree. Deliberately captured snapshots of mutable or external state are the canonical counter-example.
A temp is a sticky note with a copied-down number; a query is a live formula in a spreadsheet cell. The formula is clearer and reusable — unless you specifically needed yesterday's number frozen, or the formula is expensive to recompute.
saying these in an interview costs you the question
- Applying it to an expression with side effects, so the side effect now fires once per use instead of once.
- Applying it to a temp that intentionally snapshots mutable or time-dependent state, and calling that behaviour-preserving.
- "It's a performance optimisation" — it normally adds recomputation; the motivation is structural (unblocking Extract Function).
- Attempting it on a loop accumulator that is assigned on every iteration.
- Believing the catalog has one canonical direction — Extract Variable goes the opposite way and is equally legitimate in the right context.