Skip to main content
Real-time sync is the headline feature of Convex (and therefore vly). Every Convex query is a subscription: when a client calls useQuery(api.tasks.list, { teamId }), it receives the current results and gets pushed updates whenever those results would change.

How it works

No polling. No manual cache invalidation. No refetch() calls.

Why it matters

Less code

A typical “live dashboard” is 50 lines of polling, debouncing, and stale-data handling. With reactive queries: zero.

Lower latency

Push, not pull. Updates arrive in under 100 ms after the underlying mutation commits.

Multi-user out of the box

Two users editing the same data see each other’s changes immediately. No extra plumbing.

Predictable cost

No “1-second polling × 10,000 users = 10k req/s” surprise bills.

Common patterns

A useQuery for each chart. As underlying data flows in, the charts auto-update.

Limits

  • ~10,000 concurrent subscribers per query (soft; raisable)
  • A query with very expensive computation will re-run on every relevant mutation; design queries to be cheap
  • Subscriptions consume a small amount of bandwidth even when idle (~1 KB/s per active subscription)

Tips

Subscribe close to where data is consumed. A useQuery near a deeply-nested component re-runs only when its data changes — better than passing data down through five levels of props.
For “send and forget” mutations, fire-and-forget is fine. Convex mutations return promises but you don’t have to await them in a useEffect; the query subscription handles the update.

Convex queries

The function type that powers reactive sync.

Real-time sync patterns

Architectural patterns.

Real-time chat recipe

A worked example.
Last modified on April 18, 2026