An API for offline sync

Backends for mobile apps Senior Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Predict the output

What do the two calls return?

Python 3.13
changes = [
    (101, "note-1", "edit"),
    (102, "note-2", "create"),
    (103, "note-1", "delete"),
]


def since(cursor: int) -> tuple[list[str], int]:
    newer = [c for c in changes if c[0] > cursor]
    next_cursor = newer[-1][0] if newer else cursor
    return [f"{c[1]}:{c[2]}" for c in newer], next_cursor


print(since(101))
print(since(103))

Options
Hint 1 of 1

Which versions are greater than 101?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A (['note-2:create', 'note-1:delete'], 103) then ([], 103)

Correct. Changes after 101, then nothing new after 103.

The cursor is the id of the last change the client has. since(101) returns everything after it and moves the cursor to the newest id; asking again with 103 finds nothing and leaves the cursor as is.

Why not the others?
  • B (['note-1:edit', 'note-2:create', 'note-1:delete'], 103) then ([], 103). Cursor 101 excludes change 101.
  • C (['note-2:create'], 102) then ([], 103). All newer changes are returned at once.
  • D (['note-2:create', 'note-1:delete'], 103) then ([], 0). With nothing new the cursor stays the same.

30-second answer

Give each change a server-side, monotonically increasing version, and expose 'give me changes since cursor N', returning changes plus the next cursor, including deletions as tombstones. Uploads carry client-generated ids and a base version so retries are idempotent and conflicts are detectable; resolve them with a rule per field (last writer wins, merge, or ask the user). Keep tombstones long enough that offline devices still see deletions.

Practise saying it · 35 s. No recording, just a timer

Offline sync API.

Say your answer out loud, then check which of these points you covered:

  • Server version + cursor
  • Tombstones
  • Client ids, idempotent uploads
  • Conflict rules
  • Resync signal

Deep dive

A per-user change log or a global sequence both work; index (user_id, version).

Return a 'resync from scratch' signal if the cursor is older than tombstone retention.

Batch uploads and acknowledge per item.

CRDTs merge some data types automatically.

Changes since a cursor Ran on Python 3.13.5 · mypy --strict
Python 3.13
changes = [
    (101, "note-1", "edit"),
    (102, "note-2", "create"),
    (103, "note-1", "delete"),
]


def since(cursor: int) -> tuple[list[str], int]:
    newer = [c for c in changes if c[0] > cursor]
    next_cursor = newer[-1][0] if newer else cursor
    return [f"{c[1]}:{c[2]}" for c in newer], next_cursor


print(since(101))
print(since(103))

Common mistakes

  • Syncing by updated_at timestamps from client clocks.
  • Deleting rows outright, so offline devices never learn about it.
  • No idempotency on uploads.

Senior take & follow-ups

Server versions and cursors, tombstones, idempotent uploads, conflict rules.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why not timestamps?

    Show hint

    Clock skew and equal timestamps lose changes; a server sequence doesn't.

  2. How long keep tombstones?

    Show hint

    Longer than your longest supported offline period, then force resync.

Similar on Android: Handling offline and flaky networks The Android page queues work on the device while offline; here the server hands back changes since a cursor.

Suggest a fix if something here is wrong or out of date.