What does PouchDB's db.sync(remote, {live: true, retry: true}) do in an offline-first app?
answer
- Two replications, one per direction
- Writes land locally first
- live keeps it running after catch-up
- retry survives dropped connections
- paused also means simply idle
basics
~20 sIt starts two continuous replications, local to remote and remote to local, that keep running as changes occur and reconnect automatically after failures. Writes land in the local database first, so the app works offline and catches up when connectivity returns.
solid answer
~50 s`db.sync` is shorthand for two replications running in opposite directions, because a CouchDB replication is one-directional. `live: true` keeps them running instead of stopping once the current backlog is drained; `retry: true` makes them wait and reconnect after a network failure rather than emitting a fatal error. The call returns an event emitter: `change` per replicated batch, `active` and `paused` as the feed becomes busy or idle, `denied` when one document is rejected, `error` for a fatal problem, and `complete` when you `cancel()`. Because PouchDB stores data locally — IndexedDB in a browser, LevelDB in Node — application writes complete immediately whether or not the device is online, and the replications resume from their stored checkpoints when it reconnects. The one trap is `paused`, which fires both when sync is caught up and when it is waiting to retry, so it is not an offline indicator.
code
javascript · 8 linesconst handle = db.sync('http://host:5984/orders', { live: true, retry: true })
.on('change', info => refreshUi(info.direction))
.on('paused', err => setStatus(err ? 'retrying' : 'up to date'))
.on('denied', err => reportRejectedDoc(err))
.on('error', err => reportFatal(err));
// long-lived resource: stop it when the session ends
handle.cancel();go deeper
Be able to say that sync starts replication in both directions, that live keeps it running and retry reconnects, and that writes go to the local database first so the app works offline.
Explain the event model precisely, especially that paused covers both idle and retry-wait, and describe how each direction resumes from its own checkpoint after a disconnect.
Show you have shipped this: cancelling handles on teardown, surfacing denied rejections, distinguishing idle from retrying in the UI, and having a real plan for conflicts created by offline edits.
Own the sync architecture — how much data each device pulls, whether a filter or a per-user database defines that subset, and what the product promises a user whose offline edit lost the tie-break.
## What PouchDB is PouchDB is a database that runs inside the client — in a browser on IndexedDB, or in Node on LevelDB — and speaks the CouchDB replication protocol. Because that protocol is nothing but a defined sequence of HTTP calls, PouchDB can be a full replication peer of a real CouchDB server. This is the whole basis of the offline-first pattern: the application talks only to the local database, and a background process keeps the local database and the server converged. ## What sync actually starts A CouchDB replication moves changes in **one** direction. `db.sync(remote, opts)` therefore starts two of them — the equivalent of `db.replicate.to(remote, opts)` and `db.replicate.from(remote, opts)` — and returns one handle that aggregates both. Each direction keeps its own checkpoint, so they resume independently. ## The two options that matter most **`live: true`** means the replication does not finish when it has drained the backlog; it keeps following the change feed and replicates each new change as it appears. Without it, sync is a one-shot catch-up that completes and stops, which is what you want for an explicit "sync now" button and not what you want for a background process. **`retry: true`** governs what happens when the connection breaks. Without it, a network failure ends the replication with an `error` event and you must restart it yourself. With it, PouchDB backs off and reconnects on its own, which for a mobile client — where losing the network is normal rather than exceptional — is almost always the right setting. ## The events The returned object is an event emitter, and reading the events correctly is most of the skill: - **`change`** — fires per replicated batch, in either direction, carrying which documents moved. This is the hook for refreshing UI state. - **`active`** — the replication has work to do and is transferring. - **`paused`** — the replication has gone quiet. **This is the trap**: it fires both when sync is fully caught up and idle, *and* when a connection failed and PouchDB is waiting to retry. The argument tells you which (an error argument means it is a retry wait, no argument means simply idle). Wiring an "offline" badge to a bare `paused` gives you a badge that lights up whenever sync succeeds. - **`denied`** — a single document could not be written to the remote, typically because of validation or per-database permissions. The rest of the replication keeps going, so an unhandled `denied` is a silent partial failure. - **`error`** — a fatal problem; with `retry: true` this becomes rare, since recoverable failures turn into retry waits instead. - **`complete`** — fires when the replication finishes. With `live: true` that only happens after you call `cancel()`. ## Cancelling The handle has `cancel()`. A live sync is a long-lived resource: in a single-page app you must cancel it when the component or the session that owns it goes away, or you will accumulate parallel syncs that all write to the same local database. ## What the user experiences offline The application reads and writes the local PouchDB instance only. Those operations succeed with no network, at local-storage latency, which is why the UI stays responsive on a bad connection. When the device reconnects, each direction resumes from its checkpoint and transfers only what is missing, using the same `_revs_diff` negotiation a server-to-server replication uses. ## Conflicts are part of the deal Because the device accepts writes while disconnected, another user can change the same document on the server in the meantime. When the two directions meet, both versions are kept as leaf revisions of the document's revision tree, and PouchDB and CouchDB independently pick the same winner. The application sees only the winner unless it asks for conflicts explicitly. An offline-first app with no conflict-handling code has not avoided the problem; it has silently accepted whichever version the tie-break rule prefers. ## Filtering the sync `sync` accepts a `filter` (a design-document filter function on the server), a `selector`, or a `doc_ids` list, so a device can pull a subset rather than an entire database. Two cautions come with it. First, filters on business fields do not match deletion tombstones, so deletions can silently fail to propagate unless the filter passes `_deleted`. Second, a filter is not a security boundary: it shapes what a replication transfers, not what the client is allowed to read. Confidentiality between users needs separate databases with their own permissions, not a clever filter. ## Practical checklist Use `live` and `retry` together for background sync. Handle `denied` — do not let per-document rejections vanish. Distinguish idle from retrying when reading `paused`. Cancel the handle on teardown. And decide up front what happens when two devices edit the same document, because with offline clients that is not a rare edge case.
- Why is the paused event a poor signal for showing an offline indicator?`paused` fires in two very different situations: sync has caught up and is idle, and sync has failed and is waiting to retry. Only the error argument distinguishes them. Binding an offline badge to a bare `paused` lights the badge every time sync succeeds. Read the argument, or track connectivity separately and use `paused` only for a quiet-versus-busy indicator.
- What is the difference between omitting retry and setting retry to true?Without `retry`, any network failure ends the replication with an `error` event and it stays dead until the application restarts it. With `retry: true`, PouchDB backs off and reconnects itself, surfacing the failure as a `paused` event carrying an error instead. For a mobile client, where connectivity drops routinely, retry is effectively mandatory.
- Two devices edit the same document while both are offline. What does the app see after they sync?Both versions survive as leaf revisions of the same document, and both PouchDB and CouchDB pick the same winner deterministically. A normal read returns only that winner, with no indication the other version exists. The application must ask for conflicts explicitly and merge them; otherwise one user's edit is quietly shadowed.
saying these in an interview costs you the question
- Thinks one sync call is a single bidirectional replication
- Treats the paused event as proof the device is offline
- Leaves a live sync running after the component unmounts
- Assumes offline writes are queued rather than committed locally
- Ignores denied events as harmless