intermediateOptimize
Why not inline every function?
Whether the candidate reasons about optimization as a budget rather than a good deed. The discriminator is whether they can name a case where inlining makes the program measurably slower, not merely bigger.
What a strong answer covers
- Because inlining trades size for call overhead, and size is not free. Every inlined copy is more instructions in the binary and more pressure on the instruction cache. A hot loop whose body no longer fits in L1i runs slower after inlining even though it executes fewer instructions, and this is the case people do not predict.
- It is also quadratic in the wrong situations. Inlining A into B makes B bigger, which makes inlining B into C more expensive, and a naive inliner applied to a deep call graph produces enormous functions and compile times to match. Register pressure rises with function size, so past a point you inline a call away and buy a spill.
- Recursion is the obvious hard stop — you cannot fully inline a recursive call without a termination argument, so inliners bound it by depth. Anything whose target is not known statically cannot be inlined at all until devirtualization or a JIT-installed guard makes the target known.
- So real inliners are heuristics: callee size, call-site hotness, whether the call is in a loop, whether inlining exposes constants that will fold, whether the function is called once, whether it is marked. The heuristic is the optimization. That is also why forcing inlining by hand tends to lose to the compiler over the life of a codebase — the annotation does not update when the profile changes.
✓ Green flags
- Names instruction-cache pressure specifically, not just "the binary gets bigger".
- Connects inlining to register pressure and spilling.
- Mentions compile time and the blow-up on deep call graphs.
- Knows indirect and virtual calls need the target resolved first, and names devirtualization or a JIT guard.
- Frames the real benefit correctly: not the saved call instruction but the constants, branch folding and specialization that the inlined body now exposes.
✗ Red flags
- "Inlining is always faster, it removes the call overhead." The call overhead is a few instructions and often the least of it; the size cost is what bites.
- "Only recursion stops it." Recursion is the easy case; size, budget and unknown call targets stop far more of it in practice.
- "Use force-inline everywhere and let the compiler sort it out." A force-inline hint overrides the size heuristic, which is exactly the heuristic that was protecting you.
- "Inlining is a source-level thing, it happens before optimization." It happens repeatedly, interleaved with other passes, because each round exposes work for the next.
Follow-up
Your build got 30 percent slower and the binary grew 40 percent after someone raised the inlining threshold. How do you find out whether it helped anything?
Implementation challenge
What to ask them to write or trace on a whiteboard.
Design the inliner's decision function. What inputs does it take, what does it return, and where in the pass pipeline does it run relative to constant propagation? Justify the ordering.