Resumability and the Optimizer
1. What is resumability, and how does it differ from hydration?
Resumability means the browser continues from where the server stopped, without re-running components to rebuild state and attach listeners.
A hydrating framework sends HTML, then downloads the component code and executes it again on the client so it can find the event handlers and recreate state.
Qwik serializes the application state, the component boundaries and the listener locations into the HTML, so the client has nothing to replay at startup.
The resumability docs frame the result as startup cost that does not grow with app size: the page becomes interactive with a small loader script, and component code downloads only when an interaction or a state change needs it.
A strong candidate adds the trade-off. The first click on a cold page may wait for a network fetch of that handler's chunk, which is why prefetching and preloading matter (question 5), and the serialized state makes the HTML larger.
A weak answer calls it "lazy hydration" or "partial hydration". Islands still hydrate each island; Qwik does not run the component on the client at all until its state changes and it has to re-render.
2. What does the $ suffix mean, and what does the Optimizer do with it?
A trailing $ marks a lazy-loading boundary.
The Optimizer, a build step in the Qwik Vite plugin, finds each function passed to a $ API, moves it into its own module (a segment, sometimes called a symbol), and replaces it with a QRL: a serializable reference that names the chunk and export to import later.
Per the dollar sign docs, any API ending in $ follows this contract, including component$, onClick$, useTask$, routeLoader$ and server$.
import { component$, useSignal } from '@builder.io/qwik';
export const Counter = component$(() => {
const count = useSignal(0);
return (
<button onClick$={() => count.value++}>
Clicked {count.value} times
</button>
);
});
Here the Optimizer produces at least two segments: the component render function and the click handler.
The server renders HTML whose button carries an attribute pointing at the handler's chunk, and the handler's code is fetched only when someone clicks.
Candidates should know you can create your own boundary with $() and that the Qrl forms (componentQrl, useTaskQrl) are what the $ forms compile to.
3. What can a function inside a $ boundary capture?
Three kinds of references are allowed: top-level exported symbols, which the extracted module can import; const variables from the enclosing scope whose values are serializable; and values Qwik already knows how to serialize, such as signals and stores.
The serialization guide states the rule: only serializable data can cross a $ boundary, and a non-top-level variable must be declared with const.
The trap is that some violations pass static analysis and fail at runtime. The guide says capturing a non-serializable value "will fail at runtime because Qwik does not know how to serialize it."
Instances of custom classes are the common case. The ESLint plugin's qwik/valid-lexical-scope rule flags many of these at edit time, and a candidate who has shipped Qwik will mention running it in CI.
A strong answer explains why the rule exists. The extracted function will run later, possibly in another environment, with only the serialized snapshot of its captured variables.
A let would need its later reassignments tracked, and a class instance would need its prototype and private state rebuilt, which the serializer cannot do.
4. How does Qwik serialize state, and what does noSerialize do?
During SSR, Qwik walks the reachable state of signals, stores, captured variables and QRLs and writes it into the page so the client can rebuild those objects on demand. In Qwik 1.x that state sits in a <script type="qwik/json"> block.
In v2, per the v2 migration guide, it moves into qwik/vnode and qwik/state script blocks, which matters for any tooling that parsed the old tag.
The serialization guide lists promises as serializable, so a pending value can be sent and resolved on the client.
noSerialize(value) marks a value to be left out. The value works during the current execution, and after the page resumes on the client it reads as undefined.
Use it for third-party objects like a map widget, an editor instance or a WebSocket, and re-create them on the client when needed.
import { component$, useSignal, useStore, noSerialize, type NoSerialize } from '@builder.io/qwik';
export const MapView = component$(() => {
const el = useSignal<HTMLDivElement>();
const state = useStore<{ map: NoSerialize<MapLib> }>({ map: undefined });
return (
<div
ref={el}
onClick$={async () => {
if (!state.map) {
const { createMap } = await import('./map-lib');
state.map = noSerialize(createMap(el.value!));
}
}}
/>
);
});
A strong candidate also watches the size of serialized state. Loading a 2 MB API response into a store puts that payload in the HTML, so the fix is to select the fields the page renders before they reach a signal.
5. What is qwikloader, and how does a click become a code download?
Qwikloader is a small inline or module script that registers global listeners for the event types the page uses.
On each event, it walks from the target up the DOM looking for an element with a matching handler attribute, reads the QRL from it, imports that chunk and calls the exported handler with the event and the element.
In 1.x these attributes look like on:click; in v2 they are q-e:click, q-d:click and q-w:click for element, document and window listeners. JSX is unchanged, so application code does not see the difference.
The cost of a cold network fetch on first interaction is handled by preloading.
Qwik 1.14.0 added automatic bundle preloading based on the bundle graph the build emits, and later 1.x releases tuned it, for example 1.19.0 stopped merging useVisibleTask$ code into other segments to avoid preloading too much.
A candidate should be able to explain how the build knows which chunks are likely next and how to inspect the bundle graph when a page preloads more than it should.
6. What does this handler do, and why is e.preventDefault() a problem?
<a
href="/checkout"
onClick$={(e) => {
e.preventDefault();
openCartDrawer();
}}
>
Cart
</a>
On a resumed page, the handler code may not be loaded when the click happens. Qwikloader has to fetch the chunk, and by the time the handler runs the browser has already acted on the event, so the link navigates to /checkout.
Calls to preventDefault() and stopPropagation() only work when they run synchronously inside the dispatch.
Qwik gives two fixes. The declarative one is the preventdefault:click attribute on the element, which qwikloader honors before loading anything.
The programmatic one is sync$(), which inlines a small serialized function into the HTML so it runs during dispatch; pass an array of handlers when you also need the lazy part.
<a
href="/checkout"
preventdefault:click
onClick$={() => openCartDrawer()}
>
Cart
</a>
<input
onKeyDown$={[
sync$((e: KeyboardEvent) => {
if (e.key === 'Enter') e.preventDefault();
}),
$(() => submitSearch()),
]}
/>
A strong candidate adds that sync$ bodies cannot capture variables, because they are inlined as source text, and keeps them short.