FullStack Course LogoFullStack Course
Module: Machine Coding
Machine Coding·241·11 MIN READ

241: Async Data and TanStack Query v5 in Machine Coding

TOPICS COVERED: Async Data and TanStack Query v5 in Machine Coding

Learning outcomes

By the end of this lesson, you should be able to:

  • explain and apply query ownership in a realistic implementation;
  • distinguish and implement loading, empty, error, and success states in a realistic implementation;
  • explain and apply mutations in a realistic implementation;
  • explain and apply optimistic UI in a realistic implementation;
  • explain and apply pagination and filters in a realistic implementation.

Prerequisites and retrieval

This lesson assumes that you have completed the earlier 01–06 foundation and the preceding lessons in this module. Before you begin, retrieve one concrete example from a previous project in which one of these concerns appeared. You are not trying to memorize a list of terms. You are preparing to make and defend a design decision in a timed machine-coding exercise, where the implementation still needs to be readable, accessible, testable, and straightforward to extend under interview pressure.

Terminology

  • Query ownership: Use useQuery({ queryKey, queryFn }) to own fetched server state instead of copying the result into local component state.
  • Loading, empty, error, success: These are four distinct user-visible states. Treating them separately is a precise engineering decision, not just a vocabulary exercise.
  • Mutations: Use useMutation({ mutationFn, ... }) for remote writes. The mutation owns the write lifecycle, while the query cache must be invalidated or updated when the write changes data that is being displayed.
  • Optimistic UI: Update the interface before the server confirms a write, but only when the update and its rollback behavior are understandable.
  • Pagination and filters: Put server-affecting filters, pages, or cursors in the query key so each result has the correct cache identity.
  • Mock API boundaries: In a challenge, a mock transport should preserve asynchronous and error semantics. Returning hardcoded values synchronously hides the loading and failure behavior that the implementation is supposed to handle.

Mental model

Treat Async Data and TanStack Query v5 in Machine Coding as a design problem with observable inputs, outputs, invariants, and failure modes. A server-state library can save time in a machine-coding round, but only if you use it with discipline. TanStack Query v5 should own the remote cache state; components should own local UI state such as an open menu or an input that has not yet been submitted. A strong implementation makes its assumptions visible, reduces uncertainty at system boundaries, and leaves enough evidence - in tests, types, constraints, metrics, or diagrams - to show why the design is safe.

A useful sequence for both an interview solution and production work is:

text
requirement -> constraints -> model -> implementation -> failure analysis -> verification

Do not move directly from a requirement to a library call. First state what must remain true. Then choose the mechanism that enforces that invariant. For example, if changing a server-side filter must never display data for the previous filter, the query key needs to represent that filter; a local copy of the last response does not establish the same guarantee.

Deep dive

1. Query ownership

When data comes from the server, use useQuery({ queryKey, queryFn }) as the owner of that fetched state instead of copying the result into local state. The query key identifies which server result is being requested, and the query function performs the fetch. Derive filtered or sorted presentation from query data and local controls rather than maintaining a second, potentially stale copy of the response.

Decision rule: Choose query ownership deliberately when it makes the contract or invariant easier to prove. If it merely saves a few lines while hiding an assumption about freshness, identity, or synchronization, prefer the more explicit design.

2. Loading empty error success

Users need different feedback for a request that is still pending, a successful request that returned zero rows, a failed request, and a successful request with data. A zero-row response is not an error. Likewise, a background refetch should not automatically erase data that was already rendered just because a newer request is in flight. Keeping these states distinct lets the UI communicate what is actually happening and prevents a transient refetch from looking like an empty result.

Decision rule: Model loading, empty, error, and success deliberately when doing so makes the contract or invariant easier to prove. If a single boolean or broad fallback reduces typing but hides an important state distinction, use the more explicit design.

3. Mutations

Use useMutation({ mutationFn, ... }) for remote writes such as creating an item or changing its status. Validation and conflict failures should be visible to the user rather than swallowed. After a successful write, invalidate or update every relevant TanStack Query v5 cache entry so the UI does not continue presenting a stale list. The mutation describes the write lifecycle; it does not automatically make every related query result current.

Decision rule: Use mutations deliberately when they make the write contract or its invariant easier to prove. If they only reduce typing while leaving error handling and cache consistency implicit, prefer the more explicit design.

4. Optimistic UI

Optimistic UI is useful when the expected interaction should feel immediate, but it creates a temporary client-side claim that the server may reject. Use it only when the rollback is understandable. The usual sequence is to cancel relevant queries, snapshot the previous data, apply the optimistic update, restore the snapshot on error, and invalidate or reconcile the query after settlement. Without that final reconciliation, the client can remain different from the server even after the request has completed.

Decision rule: Use optimistic UI deliberately when it makes the interaction contract or invariant easier to prove. If the update affects several related resources, the rollback is ambiguous, or the operation is too risky to represent locally, a pending state followed by a confirmed refetch may be the safer design.

5. Pagination and filters

Any filter, page number, or cursor that changes what the server returns belongs in the query key. That gives each server result a distinct cache identity and prevents one parameter combination from being mistaken for another. A filter that only changes local presentation can stay outside the key, because it does not change the fetched result. The distinction is whether the control affects the request, not whether it happens to be rendered beside the list.

Decision rule: Handle pagination and filters deliberately when the query key makes the request contract or cache invariant easier to prove. If a value is purely visual, putting it in the server query key adds unnecessary cache entries; if it changes the server result, leaving it out risks incorrect data reuse.

6. Mock API boundaries

A mock API in a machine-coding exercise should behave enough like a real transport to exercise the important UI states. Preserve asynchronous behavior and make failures possible instead of returning hardcoded values synchronously. Otherwise, loading indicators, retry behavior, error messages, and recovery paths can appear correct in code while never being exercised.

Decision rule: Use mock API boundaries deliberately when they make the contract or invariant easier to prove. If the mock only makes the happy path convenient while hiding timing and failure assumptions, use a small asynchronous boundary that exposes those behaviors instead.

Worked example

Consider a timed machine-coding exercise for a feature that must remain readable, accessible, testable, and easy to extend while the interview clock is running. Start by expressing the requirement in one sentence. Then list the input and output contracts and identify which concept owns each failure mode. The key design move is separation: parsing and validation belong at the boundary; domain rules belong in the domain or service layer; persistence rules belong in the database or repository; and presentation rules belong in the client. Mixing these responsibilities can make a happy-path demo look shorter, but it makes edge cases much harder to reason about.

For example, the list query owns the server result for a particular set of filters, while the create mutation owns the remote write. The successful mutation then invalidates the list queries so a later render can use fresh data:

tsx
const listQuery = useQuery({
  queryKey: ['items', filters],
  queryFn: ({ signal }) => api.items.list({ filters, signal }),
});

const createItem = useMutation({
  mutationFn: api.items.create,
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['items'] }),
});

The filters value is part of the key because it affects the list request. The signal gives the request a cancellation boundary, and the mutation's success handler reconciles the related list cache rather than assuming that the old list is still accurate.

Walk through at least four cases: the normal path; an empty or missing value; a duplicate, retry, or concurrent path where that case is relevant; and a dependency failure. For each case, state which layer detects the problem and what the caller observes. For instance, malformed input should be rejected at the boundary, while a dependency failure should become an explicit error state and should not be mislabeled as an empty list. That level of reasoning is what a senior code review or technical interview is testing.

Production perspective

Production correctness is broader than “the code works on my machine.” Ask how the design behaves during deploys, retries, partial failures, stale clients, concurrent requests, malformed data, schema changes, and high-cardinality workloads. Prefer explicit contracts, bounded resource usage, structured errors, and measurable behavior. Optimize only after you can identify the bottleneck or risk with evidence.

When the topic involves an external dependency, define both a timeout and a cancellation strategy. When it involves persistence, define the transaction and consistency expectations. When it exposes user-visible state, define loading, empty, error, stale, and success behavior. When security is involved, assume that the client can be modified and that network input is untrusted. TanStack Query can coordinate cache state, but it cannot replace server-side authorization or validation.

Guided lab

Build a paginated and filterable issue list using TanStack Query v5 object syntax. Include one create mutation, one optimistic toggle with rollback, and explicit loading, empty, error, and background-refresh states.

Complete the lab with this discipline:

  1. Write the requirement and two non-requirements.
  2. List the input, output, and error contracts before implementation.
  3. Implement the smallest correct vertical slice.
  4. Add at least one invalid-input test and one edge-case test.
  5. Instrument or inspect the behavior instead of guessing.
  6. Refactor one hidden assumption into an explicit type, constraint, function, or configuration.
  7. Explain one alternative design and why you did not choose it.
  8. Record a short “what would break at 10× scale?” note.

The lab should make the state transitions observable. Exercise an initial load, a successful empty result, a failed request, a background refresh with existing data, a successful create, and a rejected optimistic toggle. A mock that never delays or fails cannot verify those paths, even if the component contains branches for them.

Edge cases and failure modes

  • Query ownership: Test absence, malformed input, duplicates, ordering or concurrency where applicable, and behavior at the smallest and largest credible sizes.
  • Loading empty error success: Test absence, malformed input, duplicates, ordering or concurrency where applicable, and behavior at the smallest and largest credible sizes.
  • Mutations: Test absence, malformed input, duplicates, ordering or concurrency where applicable, and behavior at the smallest and largest credible sizes.
  • Optimistic UI: Test absence, malformed input, duplicates, ordering or concurrency where applicable, and behavior at the smallest and largest credible sizes.
  • Pagination and filters: Test absence, malformed input, duplicates, ordering or concurrency where applicable, and behavior at the smallest and largest credible sizes.

These are not only data-shape tests. Check the observable result as well: which query key is used, whether stale data remains visible during a background refresh, whether a rejected mutation rolls back, and whether a change in page or server-side filter produces the correct cache entry.

Common mistakes and debugging

  • Solving the example instead of the requirement: a copied pattern can be syntactically correct while architecturally wrong for the actual contract.
  • Hiding uncertainty with assertions, broad exception handlers, permissive schemas, or “temporary” any values.
  • Testing only the happy path and discovering the real contracts only after integration.
  • Optimizing before measuring, or selecting a scalable mechanism without a scale requirement.
  • Letting client-side behavior stand in for server-side authorization, validation, or persistence guarantees.

When debugging, reproduce the smallest failing case first. Inspect the actual value or execution plan, trace the boundary where the invariant first becomes false, and fix the layer that owns the problem instead of adding a downstream patch. For this topic, that may mean inspecting the query key and request parameters, the Network request and response, the mutation error, or the cache update that follows settlement. A list showing no rows may reflect a valid empty response, a failed request being rendered incorrectly, or a filter that was omitted from the key; the next diagnostic step should distinguish those possibilities rather than guessing.

Interview questions

  1. What problem does Query ownership solve, and what trade-off or failure mode would make you choose a different approach?
  2. What problem does Loading empty error success solve, and what trade-off or failure mode would make you choose a different approach?
  3. What problem do Mutations solve, and what trade-off or failure mode would make you choose a different approach?
  4. What problem does Optimistic UI solve, and what trade-off or failure mode would make you choose a different approach?
  5. What problem do Pagination and filters solve, and what trade-off or failure mode would make you choose a different approach?

Checkpoint

Without notes, explain Async Data and TanStack Query v5 in Machine Coding to another developer in five minutes. Your explanation must include one invariant, one edge case, one production failure mode, and one alternative design. Then implement a small example without copying the lesson code. Be able to explain why each server-affecting parameter is represented in the query key, how the UI distinguishes an empty result from an error, and how a failed optimistic write is repaired.

Mastery checklist

  • I can define the core terms precisely.
  • I can choose a design from requirements instead of from habit.
  • I can implement and test the normal path and edge cases.
  • I can explain the runtime, storage, or complexity cost.
  • I can identify which layer owns validation, errors, and recovery.
  • I can compare at least two reasonable alternatives.
  • I can explain how the design changes at larger scale or stricter reliability.

References

Reader page: /machine-coding/lesson/241/async-data-and-tanstack-query-v5-in-machine-coding