skip to content

Of backward jumps, forward jumps to a shared exit, and jumps into the middle of a block, which resists structured rewrite?

level: middleimportance: should knowfreq 40%

answer

  1. three shapes, not one
  2. direction and target decide the cost
  3. single entry is what constructs guarantee
  4. a second entry has no wrapper
  5. split, duplicate, or add state

basics

~20 s

The jump into the middle of a block resists rewrite, because it gives that block a second entry, and every structured construct has exactly one. Backward jumps become iteration and forward jumps become a guarded region; the inward jump forces the code itself to change.

solid answer

~50 s

Two of the three have a local replacement. A **backward jump** to an earlier point is an ad-hoc loop, so the code between the target and the jump becomes an iteration body and the jump condition becomes the loop condition. A **forward jump to a shared exit** skips the rest of a region, so it becomes a guarded region: the skipped work moves inside a selection and the shared tail runs after it. The **inward jump** is different in kind. It creates a second entry point into a block whose opening statements were never executed, and no structured construct offers two entries, so you cannot swap it out in place. You have to change the code: split the block at the jump target so the tail becomes its own unit reachable from both paths, or duplicate the tail, or introduce state that records which entry happened.

code

pseudocode · 11 lines
pseudocode
i = 0
jump to middle

start:
    total = 0
    i = 0
middle:
    total = total + items[i]
    i = i + 1
    if i < count then jump to middle end
    report(total)

go deeper

for a junior

Recall that structured constructs are entered at one place. That single fact is what makes a jump landing in the middle of a block the hard case.

for a middle

Be able to name the replacement for each pattern and say which are local. The backward jump becomes iteration, the forward exit becomes a guarded region, and the inward jump forces a split.

for a senior

Show judgment about the removal itself: duplicating a tail is correct and drifts, adding state is the smallest diff and the worst result. Say which you would take in a routine without tests.

for a principal

The tradeoff to own is scope. A local pattern can be fixed on touch; a pattern that forces a split needs a characterisation test and a deliberate slot, and pretending otherwise is how a cleanup becomes an outage.

## Three patterns, not one `Goto elimination` is usually taught as a single move, which is why candidates say all jumps can be swapped out mechanically. In real old code there are three shapes, and they do not cost the same to remove. 1. **The backward jump.** Control returns to an earlier point in the same routine, usually under a condition. This is a loop that was never written as one. 2. **The forward jump to a shared exit.** Control skips the rest of a region and lands on a label that several paths share — typically reporting, releasing or returning. 3. **The inward jump.** Control lands inside a block, past that block's opening statements. Whatever those statements established has not happened. ## The replacements | Pattern | What it is doing | Structured replacement | What it costs | |---|---|---|---| | Backward jump | An unwritten loop | Iteration whose body is the span between target and jump | Usually nothing; often clearer | | Forward jump to shared exit | Skipping a region | The region guarded by a selection, tail afterwards | Deeper nesting, or a duplicated tail | | Inward jump | A second entry to a block | None in place | The block must be split, duplicated, or given state | The first two are **local**: you can perform them looking at one screen of code, and the surrounding routine is untouched. The third is not local, and that asymmetry is the point of the question. ## Why the inward jump is different in kind Every structured construct is **single entry**: a loop is entered at its head, a selection at its test, a block at its first statement. That is not a convention, it is what makes the constructs reasonable about — a reader who sees the head knows every path through the body begins there, and so every property established at the head holds for the whole body. An inward jump denies exactly that. Two distinct histories now arrive at the same statement: one that ran the block's opening statements and one that did not. Anything those statements established — a counter zeroed, a buffer allocated, a bound checked — is true on one path and unknown on the other. There is no construct you can wrap around that shape, because the shape is precisely the thing constructs are defined to exclude. ## What the rewrite actually looks like Because you cannot replace it in place, you restructure: - **Split at the target.** The part of the block from the jump target onward becomes its own named unit, and both the original path and the former jump path reach it in sequence. This is the usual answer and the only one that removes the duplication. - **Duplicate the tail.** Write the code after the target twice, once in each path. Correct, cheap to do, and it doubles the maintenance surface — the classic way a cleanup sequence drifts out of step between two copies. - **Introduce state.** Add a variable recording which entry occurred and guard the opening statements with it. This preserves behaviour with the smallest textual diff and is usually the worst outcome: the jump's non-local coupling is now a variable read at a distance, which is the same defect without the visible arrow. ## The rule worth carrying out of this Judge a jump by **direction and target**, not by the keyword: - Forward, to a single label, never into a block — cheap, sometimes the best available structure. - Backward, to the head of a region that reads like a loop — replaceable mechanically. - Into a block, or backward into the middle of one — expensive, because removing it changes the code rather than rearranging it. And note the trap in the middle column of the table: a rewrite always exists in principle, but the existence of a rewrite says nothing about whether it is **local**. A candidate who says `every jump can be replaced` is right about expressiveness and wrong about the work, and it is the work that decides whether a legacy routine gets cleaned up or quietly left alone.

  • What goes wrong if you remove an inward jump by duplicating the tail instead of splitting it?
    Behaviour is preserved on the day you do it, and the two copies then drift. The failure mode is specific: someone fixes the release order or a bound check in one copy and not the other, and the bug appears only on whichever path was not touched. Splitting keeps one copy, so a fix lands once.
  • Why is a backward jump usually the easiest of the three to remove?
    Because it already has the shape of the construct that replaces it. The span between the target and the jump is the body, the jump's condition is the loop condition, and the target is a single point every repetition enters. Nothing outside that span has to change.
  • Is a forward jump that skips over a declaration in the same class as an inward jump?
    It is closer than it looks. Control does not enter a block partway, but it reaches code below a declaration that never ran, so a later statement sees a name whose initialisation depended on a path not taken. The fix is the same family: move the declaration, or narrow the region so the skipped span and the declaration live together.

saying these in an interview costs you the question

  • Claims every jump can be swapped for a construct without touching other code
  • Says an inward jump is fine if the skipped statements look harmless
  • Treats direction and target as irrelevant to how costly a jump is
  • Assumes duplicating the tail is a clean removal with no ongoing cost
  • Calls adding a state variable a structured rewrite when it only hides the jump