skip to content

A client-side router redirects by pushing a new history entry. Why does pressing Back bounce the user forward again?

level: middleimportance: should knowfreq 54%

answer

  1. the attempted address is still there
  2. corrections versus new places
  3. one back press per user move
  4. replace on redirect and normalisation
  5. arbitrary entries cannot be deleted

basics

~20 s

Pushing leaves the pre-redirect address on the stack, so Back lands on it, the same condition redirects again, and the user is pushed forward. A navigation that corrects the current location must replace that entry, not add one.

solid answer

~50 s

A redirect is not a new place the user visited; it is a correction of the place they tried to visit. If the router pushes, the stack holds both the attempted address and the corrected one. Pressing Back returns to the attempted address, whatever condition redirected the first time is still true, and the router pushes forward again — a trap the user cannot escape with the back button. The fix is a history policy rather than a special case: a navigation to a genuinely new destination pushes, while a navigation that corrects or refines the current one replaces it. That covers redirects, filling in a default, normalising a value, and syncing a control's value into the address on every change. Replacing also keeps the stack honest, because one user-visible move should cost one back press.

go deeper

for a junior

Remember that a redirect should replace the entry it corrects rather than add one, otherwise Back lands on the address that redirects and the user is pushed forward again.

for a middle

Explain the stack step by step and state the general rule: new destinations push, corrections of the current location replace. Mention the per-keystroke variant to show the rule is not redirect-specific.

for a senior

Show that you diagnose by reproducing the stack rather than by guessing, and that you know a pushed entry cannot be removed later, so the destination must also tolerate being revisited.

for a principal

Own the policy centrally: one navigation entry point, an explicit push-or-replace decision at every call site, and a convention that stops a second navigation path from re-introducing the bug.

## The history stack is a policy your app writes A client-side router has three moves over the session's history: add a new entry, replace the entry that is current, and traverse to an entry that already exists. It cannot delete an arbitrary earlier entry, and adding an entry discards anything that was ahead of the current one. Every navigation the app performs is therefore a deliberate choice — **is this a new place, or a correction of the place we are in?** — and the back button is nothing more than the accumulated consequence of those choices. A redirect is the clearest case of a correction. The user asked for one address, the app determined that the right address is another, and only the second one is a place the user should be able to return to. ## The bounce, step by step 1. The user navigates to the attempted address. The router pushes that entry. 2. The router's own logic decides this address must become another, and **pushes** the corrected one. The stack now holds the attempt, then the correction. 3. The user presses Back. The session moves to the attempted address, which is still on the stack. 4. The condition that caused the redirect has not changed, so the router redirects again and pushes — or, on a router that replaces on the second pass, replaces the entry and lands the user exactly where they were. 5. From the user's point of view, Back did nothing, or flashed and returned. Pressing it harder does not help, because each attempt re-enters the same rule. The user's only escape is a long press on the back control to jump several entries, or leaving the site. Both are failures. ## The decision rule | Navigation | Entry policy | Why | |---|---|---| | User opens a different destination | Push | A new place to return to | | Redirect or normalisation of the current address | Replace | The attempted address must not be reachable by Back | | A default or missing value filled in on arrival | Replace | The incomplete address was never a destination | | A control's value synced into the address as the user types or drags | Replace, or debounce then push once | One back press per user-visible move | | A step forward in a multi-step flow | Push | The user expects Back to return a step | | Re-running the current address to refresh it | Replace | Nothing moved | The pattern behind the table: **push when the user's mental model says they went somewhere; replace when the app is fixing where they already are.** ## The same bug wearing other clothes - **A search field that pushes on every keystroke.** Typing eight characters costs eight back presses to leave the screen. Interviewers ask about this as a separate symptom; it is the same missing policy. - **A wizard that replaces where it should push**, so Back leaves the flow entirely instead of stepping back one screen — the mirror-image error. - **Forward entries silently lost.** Pushing from the middle of the stack discards everything ahead, so an app that pushes a correction after a traversal destroys the user's forward history as well. - **Two ways to navigate.** When part of the app writes the address directly and part goes through the router, the policy exists in only one of them and the stack becomes unpredictable. ## What replacing does not fix Replacement corrects the entry the router is standing on. It cannot clean up entries that a previous version of the policy already pushed, because arbitrary entries cannot be removed. Two consequences follow. First, a redirect chain that pushes twice before anyone noticed leaves a trap that no later fix can unwind within that session. Second, for a flow that genuinely must not be re-entered — a completed payment, a consumed one-time link — the entry policy is not enough on its own: the destination has to be able to answer a repeat visit sensibly, because the user can always traverse to it. Whether that repeat visit is *allowed* is a guard decision; that the user can *reach* it is a history fact. ## What to say in an interview Name the mechanism (the attempted entry is still on the stack), name the rule (corrections replace, new places push), and then show you know the rule is general by giving the second symptom — the per-keystroke push — rather than only the redirect case. That is the difference between having read about the bug and having owned the policy.

  • How should a filter panel that writes its state into the address handle history?
    Treat continuous changes as corrections of the current location and replace, so the whole filtering session costs one back press. If a filter change is genuinely a destination users compare against, debounce the interaction and push once when it settles. The failure mode to avoid is one entry per keystroke or drag tick.
  • Why can a fix to the policy not repair a session that is already trapped?
    Because an app can add, replace and traverse entries, but not delete an earlier one. The bad entry stays until the session ends, so the user's own back stack still holds it. This is also why the destination itself should tolerate a repeat visit rather than relying on the entry never being reachable.
  • What happens to the entries ahead of the current one when the router pushes?
    They are discarded. After the user traverses back and the app then pushes, the forward history is gone, so an app that pushes corrections on arrival destroys the user's ability to go forward again. It is another reason arrival-time normalisation must replace.

saying these in an interview costs you the question

  • Redirects should push so users can go back to the attempt
  • The back-button loop is a browser bug
  • Just remove the bad entry from the history stack
  • Pushing an entry per keystroke is harmless
  • Replacing an entry hides the destination from the address bar
  • Calling back twice in code is a fine escape hatch