skip to content

A removed component must animate out first. What does the runtime have to defer, and what breaks if the animation never reports finished?

level: seniorimportance: should knowfreq 46%

answer

  1. logical removal, then physical teardown
  2. the instance is kept alive on purpose
  3. everything hangs on one finish signal
  4. interruptions reverse, they do not restart
  5. no signal, no teardown, stranded instance

basics

~20 s

Logical removal and physical teardown split apart: the instance and its output stay alive, marked as leaving, until something signals the animation finished. If that signal never arrives, teardown never runs and every removal strands a copy.

solid answer

~40 s

Removing the data and destroying the instance stop being one event. The runtime marks the instance as **leaving**: it stays in the tree, its output stays attached to the host, the exit animation plays, and only a *finished* signal releases it to teardown. That costs. The leaving subtree is still live, so it takes clicks and focus and receives data it will never show unless it is made inert. An interruption - the item returns mid-exit - must reverse from the current visual state rather than restart. And everything hangs on one signal: a cancelled, zero-duration or suppressed animation, or an end event the host never delivers, pins the instance with teardown unrun. So completion needs a fallback, and re-entry must adopt the leaving instance rather than collide with it.

go deeper

for a junior

Know that a component with an exit animation is not gone when it is removed: it is still on screen, and its cleanup runs only once the animation has finished.

for a middle

Explain the split between logical removal and physical teardown, and why the runtime has to hold the instance until a completion signal arrives.

for a senior

Design for the signal never arriving: a timeout fallback, an inert leaving subtree, reversal on interruption, and re-entry that adopts the leaving instance instead of duplicating it.

for a principal

Decide whether animated removal belongs in shared components at all. It buys polish and costs a lifecycle that can strand instances, so it needs an owner, a documented fallback, and a test for the never-finishes path.

An exit animation turns one event into two. "This item is gone" and "this instance is destroyed" used to happen together; now the first happens immediately and the second waits. ## Two removals, not one 1. **Logical removal.** The data no longer contains the item, or the condition that included the component is now false. From the application's point of view it is gone. 2. **Visual removal.** The animation plays. The instance's output is still attached to the host, occupying space or floating above it, for as long as the animation lasts. 3. **Physical teardown.** The registered releases run, the state is dropped, and the host nodes detach. This happens only when something signals that the animation finished. For this to work, the runtime must keep the instance in its tree after the author's data says it is gone - a deliberately inconsistent middle state - and must be told when to end it. ## What the runtime holds open - The instance itself, with its state and its registered releases still pending. - Its output, still attached to the host, still laid out and still hit-testable. - Its position in the tree, so that a re-entry of the same identity can be matched against it. - Any pending work it owns: timers still tick, subscriptions still deliver, requests still resolve. That last point is the one that surprises people. A leaving instance is fully alive. It can respond to input, steal focus, log a click on something the user believes is disappearing, or receive a data push and re-render mid-flight. ## The failure modes | cause | symptom | mitigation | |---|---|---| | the finish signal never arrives | teardown never runs; one stranded instance and its nodes per removal | treat the signal as unreliable: add a timeout, or take a completion promise from the animation itself | | a zero-duration or disabled animation | no end event is emitted at all, so removal appears to hang | detect the no-animation case up front and tear down immediately | | the subtree is hidden rather than animated | the animation never starts, so it never ends | never hide the leaving subtree; animate it, or skip the deferral | | an interrupted exit restarts | the item jumps to its start state and replays | reverse from the current visual state and reuse the same instance | | the leaving subtree stays interactive | clicks and focus land on a ghost | mark it inert: ignore input, stop applying new data, pause its own work | | removal is committed only when the animation ends | the data lies for the duration; a second removal double-fires | commit the removal in data immediately and defer only the visual teardown | ## Interruption is the normal case, not the edge Lists get filtered twice in a second. A dialog is dismissed and reopened. When an identity that is currently leaving comes back, there are three possible behaviours and only one of them looks right: - **Adopt and reverse.** The leaving instance is matched, its pending teardown is cancelled, and the animation runs backwards from wherever it had reached. Continuous, and the instance keeps its state. - **Duplicate.** A second instance is created at the same position, and two copies animate against each other at once. - **Wait and rebuild.** The exit is allowed to finish, teardown runs, and a fresh instance is built - so the item visibly completes its disappearance, pops back, and has lost whatever state it held. Reversal is also why the animation should be expressed as a move between two states rather than as a one-shot sequence: a sequence that only knows how to play forwards has nowhere to reverse from. ## The reorder case is not an exit at all When a keyed list is reordered, nothing leaves: the same instance is reused at a new position. There is nothing to defer, and an exit animation contributes nothing - the item simply appears in its new place. Animating movement needs something else from the runtime: the position **before** the update and the position **after** it, handed to the author at a moment when both are known, so the difference can be animated. Which changes cause reuse rather than replacement is the reconciler's decision and belongs to the identity and keys topic; the lifecycle point here is that "animate removal" and "animate movement" need different hooks. ## Deciding how much of this to own A framework-supplied transition wrapper exists to own exactly this contract: it holds the leaving instance, listens for completion, handles interruption, and applies the transitional states. Doing it by hand with the host's own animation facilities is possible and sometimes necessary, but the hand-rolled version has to reimplement the same five obligations - defer teardown, detect completion, provide a fallback when completion never comes, make the leaving subtree inert, and adopt the leaving instance on re-entry. The obligation list is what to interrogate in a review, whichever route is taken.

  • How do you make a leaving instance safe while it is still on screen?
    Treat it as inert. Stop routing input to it, ignore data arriving for it, pause its timers and cancel work whose result it can no longer use, take it out of the focus order and out of measurements, and make sure the application's own data already excludes it so nothing new is addressed to it.
  • An item is removed and re-added before its exit animation finishes. What should happen?
    The runtime should find the leaving instance at that identity, cancel its pending teardown, and reverse the animation from the current visual state. Building a second instance leaves two copies animating at one position; waiting out the exit and rebuilding shows a visible pop and throws the instance's state away.
  • Why does animating a reorder need a different hook from animating a removal?
    Because nothing leaves. The instance is reused at a new position, so there is no teardown to defer. The author needs its geometry before and after the update, exposed at a point where both are known, in order to animate the difference; an exit animation has nothing to attach to.

A leaving instance is like an employee whose resignation has been accepted but who stays on the payroll until the handover is signed. If nobody ever signs it, the payroll never stops.

saying these in an interview costs you the question

  • Assumes removing the data destroys the instance immediately
  • Trusts the animation end event as the only completion signal
  • Restarts an interrupted exit animation from the beginning
  • Leaves a departing subtree interactive and still updating
  • Thinks a zero-duration or disabled animation still reports finished
  • Delays the data change until the animation ends