Cleanup on Unmount: Core Concepts
Component ownership includes teardown
When a component creates a resource outside Vue's reactive system, it owns the resource's lifetime. JavaScript intervals, window listeners, observers, WebSocket subscriptions, event-bus handlers, and third-party widget instances can continue after the component disappears unless explicitly released. Those callbacks may retain component state in memory, update invisible UI, or trigger duplicate work when the component mounts again.
Use onUnmounted to release resources created during setup. Store the exact callback or subscription handle returned by the API. Remove the same listener with the same target, event name, and function reference. Clear timers by their ids. Disconnect observers, close sockets when this component owns them, and call destroy methods for external libraries. Cleanup should be idempotent when the resource API allows it, because error paths and unmount paths can overlap.
Cancel asynchronous work
A component can unmount while a request is in flight. Use AbortController when the client supports cancellation, and abort in cleanup. If cancellation is unavailable, use a request identity or active flag to ignore stale results. Cancellation saves work but does not guarantee the server stopped processing; never treat a canceled client request as proof that a write did not occur.
Watchers created synchronously during setup are stopped when their component scope ends. A watcher created later in an arbitrary asynchronous callback may not be associated with that scope and can require explicit stopping. Register lifecycle cleanup synchronously and keep ownership visible. For long-lived subscriptions, retain the unsubscribe function and invoke it as soon as the owning component ends.
Composable scope cleanup
A composable can acquire a timer or subscription and use onScopeDispose to release it when the current effect scope ends. This supports cleanup when the composable is used in a component and in a manually managed effect scope. It keeps resource ownership inside the composable that created it. Provide an explicit stop function when consumers need to end the resource before the scope ends.
Distinguish unmount from deactivation
KeepAlive caches component instances. Navigating away can call onDeactivated without unmounting, so onUnmounted cleanup does not run. Pause polling or disconnect expensive observers on deactivation when appropriate, then resume on activation. A retained WebSocket may be useful for quick return or wasteful while hidden; make the product and resource policy explicit.
Route components may also be reused when only params change. The component is not unmounted, so cleanup should be tied to the watched resource identity when a new parameter starts a new request. Do not force remounts just to trigger cleanup unless resetting all local state is intentional.
Resource cleanup is a reliability concern, not only memory hygiene. Leaked listeners cause duplicate events, lingering timers consume capacity, and orphan requests can overwrite fresh state. Pair every acquisition with a cleanup path and test the behavior after unmount, cancellation, and reuse.
A cleanup function should tolerate being called after partial initialization. A network request may fail before a socket is fully open, or an observer may not have been created in an SSR environment. Store nullable handles and guard teardown so error paths do not throw a second error while the component is leaving. Cleanup should be safe when called more than once.
Cleanup on Unmount: Practice
Own a timer and an event listener
Suppose a status panel polls every 15 seconds and listens for an online/offline event. In setup, register the interval and named event callback. In onUnmounted, clear the interval and remove the exact callback from the window target. Do not define a new anonymous function during removal; it will not match the registered listener. If the panel can be kept alive, decide whether onDeactivated should pause polling and onActivated should resume it.
Avoid starting a second interval when the component reactivates. Store the timer id and make start/stop functions idempotent. If polling overlaps because a request takes longer than the interval, guard against concurrent requests or schedule the next poll after completion. Cleanup should prevent new work and stop any existing timer.
Cancel a route-driven request
Watch a project id and create an AbortController for each request. Register watcher cleanup before awaiting the result so a param change aborts the old request. On unmount, Vue stops the synchronous watcher and its cleanup runs. If an API client ignores abort, compare the current request id before updating component state. Clear or preserve old data deliberately while the new request is pending so the UI does not imply stale content belongs to the new route.
Test cancellation by starting a request, changing the route id, and resolving responses out of order. The old response must not replace the current project. Test unmount while pending and verify no state update or unhandled rejection occurs. For a write request, handle the server's idempotency and completion semantics separately; aborting the browser request cannot reverse an already-committed mutation.
Release external subscriptions
If a composable subscribes to an event bus, return an unsubscribe function or register onScopeDispose internally. Choose one clear owner. Returning both a manual stop and automatic cleanup is useful if callers need early shutdown, but make stop idempotent. Avoid exposing a raw third-party subscription object if it would let consumers bypass cleanup policy.
Test lifecycle variants
Mount, emit an event, unmount, and emit again. The component should react only while mounted. Use fake timers to verify polling stops and does not restart twice. Mock ResizeObserver or IntersectionObserver and confirm disconnect is called. Test a KeepAlive wrapper separately: deactivation should perform the chosen pause behavior, while final unmount should release all remaining resources.
Monitor for symptoms of leaks: duplicate analytics events after route navigation, increasing active sockets, timers firing for hidden views, or repeated network requests after remount. Cleanup tests catch local ownership mistakes, while browser profiling can identify long-lived references in a complex application. Include teardown in code review whenever a new listener, timer, subscription, or widget is introduced.
Use the watcher cleanup callback for resources tied to a changing source, such as one AbortController per project id. Register cleanup before awaiting the request. This ensures a new id can cancel the old work immediately, not only when the entire component unmounts. Clear pending state only for the active request so an older finally block cannot hide a newer loading indicator.
Cleanup on Unmount: Trade-offs and Interviews
Cleanup is resource policy
Closing every resource on unmount is not always correct. A shared WebSocket may be owned by an application-level service and should remain open while one view disappears. A component-local poll should stop. Define the owner and lifetime before choosing a cleanup hook. If multiple components share a resource, use a shared service with reference counting or explicit app-level ownership rather than letting each component close it independently.
AbortController can stop client-side work and prevent stale updates, but it cannot promise that a server-side write was rolled back. Use idempotency keys and an explicit operation-status endpoint for side effects that may complete after disconnect. For reads, ignore stale responses and release network resources where supported.
KeepAlive introduces a pause/resume decision; route reuse introduces a resource-identity decision. onUnmounted alone does not cover either case. Use onDeactivated/onActivated for cached views and watcher cleanup for a changing parameter. Do not remount a whole view solely to make cleanup convenient if doing so discards unsaved state.
In an interview, pair a resource acquisition with its cleanup and explain how you test it. Discuss timers, listeners, cancellation, composable scope disposal, and one case where the component does not own the shared resource. This shows cleanup follows lifetime and ownership rather than being boilerplate added at the end.
Browser APIs can fail or be unavailable in test, server, or embedded environments. Guard construction of observers and sockets at the boundary, and provide a harmless fallback when the feature is optional. Avoid making cleanup depend on a global singleton that another component may still use. If a resource is shared, its service should own reference counting or explicit app shutdown rather than each view closing it.
Cleanup can affect user experience. Aborting a read when a user navigates away is usually appropriate, but canceling a save may leave the server outcome unknown. Show a pending or recoverable state for operations whose completion matters. Use idempotency and operation status when retrying could duplicate a write. Lifecycle cleanup controls local resources; it does not replace a distributed operation protocol.
During code review, look for symmetry: where is the timer created, where is it cleared; where is the listener registered, where is it removed; who owns the socket; how are stale responses ignored? Document intentional long-lived resources and their owner. If a resource is meant to outlive the component, move it to a store or service with a clear shutdown path.
Production monitoring can help find leaks that unit tests miss. Track active sockets, subscription counts, recurring request rates after route changes, and memory over long sessions. Reproduce suspected leaks with repeated mount/unmount cycles. Verify that counts return to baseline and that navigation does not multiply handlers. A lifecycle fix should address ownership and repeated behavior, not only lower memory once.
Choose one owner for shared resources and make shutdown observable. This avoids closing a connection another view still needs. Document who can stop and restart the shared resource. Verify the lifecycle policy with repeated navigation tests.
Quiz
1. Which statement about Cleanup on Unmount is accurate?
2. When reviewing Cleanup on Unmount, which point must hold?
3. Why store the callback used to register a window listener?
4. What does AbortController guarantee for a write request?
Flashcards
Question
What is Cleanup on Unmount?
Click to reveal answer
Answer
Release intervals, event listeners, and sockets when a component is destroyed.
Question
Cleanup on Unmount: why it matters
Click to reveal answer
Answer
Intervals and timeouts must be cleared in onUnmounted or they continue after removal.
Question
Cleanup on Unmount: key detail
Click to reveal answer
Answer
Manual event listeners registered on window need a matching removal on unmount.
Question
Cleanup on Unmount: practical note
Click to reveal answer
Answer
watch and watchEffect return stop functions that halt the watcher when called.
Question
Why store the callback used to register a window listener?
Click to reveal answer
Answer
Event listener removal matches target, event type, and function identity.
Revision Notes
Key Takeaways
- 1. Intervals and timeouts must be cleared in onUnmounted or they continue after removal.
- 2. Manual event listeners registered on window need a matching removal on unmount.
- 3. watch and watchEffect return stop functions that halt the watcher when called.
Interview Tips
- • Explain Cleanup on Unmount with one concrete example from real vue work, not a textbook restatement.
- • Discuss the trade-off: when Cleanup on Unmount helps and when a lighter approach wins.
- • Place Cleanup on Unmount inside the Lifecycles and Composables phase — interviewers check that you see the progression, not isolated facts.
Cheat Sheet
Cleanup on Unmount Cheat Sheet
Phase: Lifecycles and Composables | Level: intermediate
Key points:
- Intervals and timeouts must be cleared in onUnmounted or they continue after removal.
- Manual event listeners registered on window need a matching removal on unmount.
- watch and watchEffect return stop functions that halt the watcher when called.
- Live components in routed apps can leak timers if cleanup only lives in route guards.
Tags: vue, cleanup, memory
Continue Learning
Before You Start
- No prerequisites listed.