Prompt and context
After a PWA release, some users are still controlled by an old Service Worker and load old JavaScript with new HTML, causing ChunkLoadError. Explain the lifecycle, cache versions, open tabs, skipWaiting, clientsClaim, and rollback trade-offs.
This question fits frontend, web-platform, and full-stack roles. It tests deployment safety and user experience without requiring a particular framework. Error rates and stale-cache ratios are practice data, not universal thresholds.
What the interviewer is testing
Lifecycle
The candidate should explain install, waiting, activate, and page control, especially why an updated worker waits while old clients remain open.
Asset consistency
Separate HTML, hashed JavaScript, runtime API data, and precache entries. Deleting an old cache must not break a page that still references its assets.
Adoption strategy
skipWaiting can accelerate adoption but can switch control during a page lifetime. A strong answer evaluates compatibility and user consent.
Rollback and observability
Version IDs, an expiry window, error monitoring, and a no-op worker provide a way to stop a bad rollout.
Questions to clarify first
- Do HTML and static assets use content hashes?
- Is the Service Worker URL and scope stable?
- Can users keep pages open for hours or have unsaved state?
- Is the API backward compatible with both client versions?
- Can the product prompt for a refresh at a safe moment?
- Can the platform quickly roll back the worker script and asset manifest?
A 30-second answer
“An updated worker normally installs and enters waiting while the old worker controls open pages. I would first make HTML, hashed assets, and API contracts version-compatible, then decide whether skipWaiting is acceptable. I would use revisioned precache entries and delete only known-old caches during activate.
The page would observe updatefound and controllerchange, prompt for a refresh when safe, and record the worker version, ChunkLoadError, and activation time. For a bad release, I would stop the rollout or ship a no-op worker while retaining the previous assets, then repair and advance gradually.”
Step-by-step deep answer
Step 1: Draw the lifecycle
Registration creates an installing worker. After install it becomes waiting; the old worker still controls existing pages. Once old clients close or navigate, the new worker activates and takes control.
Step 2: Make assets compatible
HTML should reference hashed assets, and APIs should tolerate old fields during a migration window. Changing only a cache name does not help if old pages request deleted chunks.
Step 3: Design update checks
Keep the worker URL stable. Browsers check byte changes on navigation and registration; long-lived sessions may call registration.update explicitly, but should not poll aggressively.
~~~js const registration = await navigator.serviceWorker.ready; await registration.update(); ~~~
Step 4: Handle waiting and prompts
Waiting is safest by default. If immediate adoption is required, check unsaved state and protocol compatibility, ask the user, then send skipWaiting through a controlled message. Handle controllerchange without creating refresh loops.
Step 5: Clean caches and roll back
During activate, delete only caches outside an allowlist. Keep a previous asset window for rollback. If a release is bad, roll back the entry point or ship a no-op worker instead of deleting every cache.
Step 6: Observe and verify
Record worker version, cache hits, activation time, controllerchange, asset 404s, and ChunkLoadError. Test old and new tabs, offline startup, refresh, staged rollout, and rollback.
Model answer
“ChunkLoadError can happen when an old worker controls a page while the deployment has already removed the chunks that page references. The new worker waits by default, so I would not globally call skipWaiting first.
I would keep a stable worker URL, use hashed assets, and maintain an API compatibility window. Precache revisions would be managed explicitly, and activate would remove only allowlisted-old caches. The page would detect a waiting worker and offer a refresh when there is no unsaved state; immediate switching would require compatibility and careful controllerchange handling.
I would stage the release and monitor worker versions, asset 404s, and ChunkLoadError. If the version is bad, stop the entry-point rollout or deploy a no-op worker while retaining old assets. Recovery must cover multiple tabs, offline startup, refresh, and a full rollback.”
Common mistakes
- Assuming install immediately controls every page.
- Overwriting unhashed JavaScript in place.
- Calling skipWaiting unconditionally and mixing versions mid-page.
- Deleting every cache during activate.
- Renaming the worker script to force an update.
- Testing only a newly opened page, not a long-lived tab.
- Breaking the API while rolling back only the frontend entry.
- Having no worker-version telemetry or no-op rollback.
Follow-up questions
Follow-up 1: Why does a new worker wait?
The old worker still controls open clients. The default lifecycle waits for those clients to close or navigate so control does not change unexpectedly.
Follow-up 2: When is skipWaiting reasonable?
When assets and APIs are backward compatible, no important state is unsaved, and immediate refresh is acceptable. Otherwise wait for user consent or navigation.
Follow-up 3: How do you prevent unbounded cache growth?
Use version or revision allowlists and delete only unused caches during activate. Keep the previous release for a defined rollback window.
Follow-up 4: How do you stop a buggy worker?
Stop the entry rollout, and if needed deploy a no-op worker or restore the known-good script. Keep old assets available while fixing the release.
Follow-up 5: How do you prove the update is safe?
Test multiple tabs, offline startup, old HTML with new assets, refresh, update prompts, API compatibility, error monitoring, and the complete rollback path.