What is the Extract Function refactoring (also called Extract Method), what are its mechanical steps, and what signals tell you a fragment is worth extracting?
answer
- Name the intent, not the implementation
- Reads → parameters, writes → return
- Comment you'd write = function name
- Inline Function is the inverse
- Watch early returns and try/finally
basics
~20 sTake a chunk of code inside a function, move it into a new function whose name says what it does, and call that new function from the original spot. Behaviour stays identical; only the structure changes.
solid answer
~50 sExtract Function moves a coherent fragment of a function body into its own named function and replaces the fragment with a call. Mechanics: name the new function after its intent, not its implementation; copy the fragment out; every local variable the fragment reads becomes a parameter; a variable the fragment writes that is still needed afterwards becomes the return value; if there are several such written variables, that is a signal to slice the extraction differently or return a small result object; replace the fragment with the call; run the tests; then look for duplicate fragments elsewhere and point them at the new function too. The trigger is not line count but the gap between what the code says and what it means: if you would write a comment above a block to explain it, that comment is a function name waiting to happen. Extract Function is the workhorse used inside most other catalog refactorings.
code
pseudocode · 14 lines// before
function printOwing(invoice) {
let outstanding = 0
for (o in invoice.orders) outstanding += o.amount
// print details
print("name: " + invoice.customer)
print("amount: " + outstanding)
}
// after: the comment became a function name
function printOwing(invoice) {
const outstanding = calculateOutstanding(invoice) // reads -> parameter, write -> return
printDetails(invoice.customer, outstanding)
}go deeper
Define it plainly (move a block into its own named function and call it), and mention that tests must stay green because behaviour must not change.
Give the mechanics precisely — reads become parameters, writes become return values — and name the trigger as "a comment you were about to write" rather than line count.
Discuss edge cases (early returns, try/finally, multiple written variables), the Inline Function inverse and the inline-then-re-extract pass, and how Extract Function underpins Decompose Conditional, Replace Temp with Query and Extract Class.
Frame it as the atomic step that makes large restructurings safe and reviewable: small behaviour-preserving commits, tests green throughout, tool-assisted where possible, and separated from behaviour-changing commits so reviewers and bisects can tell them apart.
### First, what "refactoring" means A **refactoring** is a change to the internal structure of code that makes it easier to understand and cheaper to change, **without altering externally observable behaviour** — same outputs for the same inputs, same side effects, same errors. Named catalogs (most famously Martin Fowler's *Refactoring*) give each transformation a **name**, a **motivation**, and **mechanics**: a numbered recipe of tiny steps, each small enough that you can run the tests after it and know exactly what broke. ### Extract Function **Motivation.** A function is doing several things at different levels of abstraction. Reading it forces you to reconstruct intent from implementation details. Extracting a fragment into a well-named function replaces "how" with "what" at the call site, and the details drop one level down where you only look if you care. **Mechanics (behaviour-preserving, one small step at a time):** 1. Create a new empty function. Name it after **what it does**, not how — `isEligibleForDiscount()`, not `checkFlagsAndDates()`. If you cannot name it, that is evidence the fragment is not actually a coherent idea; adjust the boundaries. 2. Copy the fragment into the new function's body. 3. **Read set → parameters.** Any local variable the fragment reads but does not declare becomes a parameter. 4. **Write set → return value.** Any variable assigned inside the fragment and still used after it must come back. If exactly one, return it. If two or more, either extract a different slice, extract twice, or return a small value object (which is itself the *Introduce Parameter Object* / result-object move). 5. Replace the original fragment with a call to the new function. 6. Compile and run tests. 7. Scan for other code that does the same thing and replace it with a call — extraction is how duplication gets removed. ### Signals that a fragment should be extracted - You were about to write an **explanatory comment** above it. - The fragment sits at a **different level of abstraction** than its neighbours (low-level string fiddling next to high-level workflow steps). - It is **duplicated** — even approximately. - It is **conditionally guarded** by a complex boolean you would like to name. - You want to **test it** or **reuse it** independently. Length is a weak signal. A one-line extraction is perfectly legitimate when the name adds meaning: replacing `if (d.status == 3 && d.exp > now)` with `if (isActive(d))` is a real gain. Conversely a thirty-line function that reads as one coherent step may be fine. ### The inverse: Inline Function **Inline Function** is the mirror image: replace a call with the body of the called function and delete the function. Motivation: the body is as clear as the name (a function whose whole body is `return this.rating` is noise); or the codebase has accumulated a thicket of badly-factored small functions and you want to inline them all back together before re-extracting along better seams. That "inline then re-extract" pass is a standard technique — you do not have to get the decomposition right in one move. **Inline mechanics:** verify the function is not polymorphic (an overridden method cannot be safely inlined, because the call site may dispatch to a subclass); find every caller; replace each call with the body, adapting names; run tests after each; delete the function. ### Edge cases and hazards - **Side effects.** If the fragment mutates shared state, reads a clock, or performs I/O, extraction is still behaviour-preserving as long as you do not change *when* it runs. Do not "helpfully" move the call earlier or later. - **Early return / break / continue** inside the fragment. A `return` in the middle of an extracted block cannot simply move — the extracted function returning does not return from the caller. You must convert it to a returned value the caller acts on, or choose a different boundary. - **Exceptions** propagate identically, so they are usually safe, but a `try/finally` split across the boundary is not. - **Overload of parameters.** If the extraction needs six parameters, the seam is wrong, or the data belongs together (see Introduce Parameter Object), or the function belongs on another object (see Move Function). ### Why it matters beyond tidiness Extract Function is the primitive that other refactorings are built from: *Decompose Conditional* is Extract Function applied to condition and branches; *Replace Temp with Query* is Extract Function applied to the expression assigned to a temp; *Extract Class* is a sequence of extractions followed by moves. Interviewers ask about it because fluency here predicts whether someone can make a large restructuring safely in small steps rather than a risky rewrite.
- When would you apply Inline Function instead?When the body is at least as clear as the name (a delegating one-liner), or when a group of small functions has been factored along the wrong seams — inline them all back into one place, then re-extract along better boundaries. Do not inline a method that is overridden by subclasses, because call sites dispatch polymorphically.
- The fragment you want to extract assigns three local variables that are used afterwards. What do you do?Do not return a tuple of three loose values as a reflex. Either pick a different extraction boundary, extract three smaller functions (often each becoming a query), or introduce a small named result object if the three values genuinely belong together as a concept.
Like adding headings and sub-sections to a wall of prose. The words do not change, but a reader can now skim the headings and dive into only the section they care about.
saying these in an interview costs you the question
- "Extract anything longer than N lines" — line count is a proxy, not the criterion; the criterion is a coherent, nameable idea at a consistent level of abstraction.
- Naming the extracted function after its implementation (`loopOverOrdersAndSum`) rather than its intent (`calculateOutstanding`).
- Claiming extraction improves runtime performance — it is behaviour-preserving structural change; performance is at best neutral and should be measured, not assumed.
- Extracting a fragment containing a mid-block `return` and assuming the caller still returns early.
- Treating refactoring as "cleanup done later in a separate ticket" rather than continuous small steps taken with tests green.