Firebase Offline Sync & Conflict Resolution Deep Dive¶
Question ID: advanced-30
Difficulty: Senior
Tags: firebase, sync, reliability
Core Concept¶
Firebase Realtime Database queues offline writes locally; when reconnected, syncs to cloud. Conflicts arise when device and cloud both modify same recordβresolution requires strategy to prevent silent data loss.
Key Areas Covered¶
Offline Write Queuing¶
- Automatic queueing: Writes enqueued locally while offline; persisted to disk
- Sync on reconnect: Upon reconnection, queued writes synced to cloud atomically (FIFO order preserved)
- Visibility: App sees updates immediately (optimistic), cloud sees them moments later
- Risk: If both device and cloud write same field, conflict occurs
Conflict Resolution Strategies¶
Last-Write-Wins (LWW)¶
- Simple: Server timestamp determines winner; latest write overwrites
- Problem: Earlier write discarded silently (data loss)
- Example: User edits note offline at 2pm, cloud updated by another device at 1:50pm β user's edit lost
Timestamp-Based¶
- Better than LWW: Compare timestamps to resolve
- Clock skew risk: Device clock can be wrong (set to year 2000); cloud timestamp older than device
- Advantage: No explicit versioning needed
- Disadvantage: Doesn't preserve intent; both writes might be important
Vector Clocks¶
- Causality tracking: Each write tags with version number; detects concurrent writes
- Merge function: Custom logic decides which write "wins" or merges both
- Advantage: Preserves all edits; merge can combine them intelligently
- Disadvantage: Complex to implement; requires custom conflict resolution
CRDTs (Conflict-free Replicated Data Types)¶
- Append-only logs: Instead of mutable fields, record all changes as immutable events
- Merge properties: Any two histories can be merged without data loss (commutative)
- Example: Counter CRDT: increment A by 2, increment B by 3 β final value 5 (order doesn't matter)
- Advantage: No conflicts (by design); all edits preserved
- Disadvantage: Non-obvious semantics; hard to retrofit into existing apps
Firestore Transactions¶
- Multi-document ACID: All reads/writes atomic; other clients see consistent snapshot
- Offline limitation: Offline writes can't participate in transaction
- Result: Transaction committed without offline write, then offline write syncs β atomicity broken
- Mitigation: Avoid transactions across offline-writable fields
Real-World Issue: Data Loss¶
Time 1: User A (device offline) writes note = "Important task"
Time 2: User B (cloud online) writes note = "Deleted by accident"
Time 3: User A reconnects; offline write synced
Result with LWW: If User B's write is later, User A's data lost silently
Real-World Patterns¶
Pattern: Last-Write-Wins Problem¶
// Offline write
firebase.setValue("task", "My task") // Queued
// Meanwhile, cloud updated (another device)
firebase.setValue("task", "Overwritten") // Server timestamp T+10s
// On sync: LWW sees T+10s > T+0s, server write wins
// Result: Offline write lost silently
Pattern: Vector Clock Merge¶
Device history: [write1(name="Alice", T=1), write2(age=30, T=2)]
Cloud history: [write1(name="Alice", T=1), write3(email="a@b.com", T=1.5)]
Merge: {name="Alice", age=30, email="a@b.com"} // All three writes preserved
Pattern: CRDT Counter Example¶
Offline increment: counter += 5 // Logged as [device_id, 5]
Cloud increment: counter += 3 // Logged as [server_id, 3]
Merge: Total = 5 + 3 = 8 // Both increments preserved, order irrelevant
Traditional merge (LWW): Would pick one (data loss)
Pattern: Offline Transactions (Anti-pattern)¶
// Problem: Offline transaction not atomic
runTransaction {
val user = read("users/1")
user.gold -= 100
val item = read("items/1")
item.quantity -= 1
writeUser(user)
writeItem(item)
// If offline here: both writes queued but separate from transaction context
}
// Better: Single path update (atomic)
firebase.setValue("users/1/gold", userWithGold) // Atomic
Tradeoffs¶
| Factor | Optimistic Sync | Pessimistic (Require Online) |
|---|---|---|
| UX Responsiveness | Immediate (offline writes visible) | Delayed (blocked until online) |
| Conflict Risk | High (concurrent writes) | None (network enforces serialization) |
| Battery | Better (no polling) | Worse (checks connection) |
| Complexity | High (conflict resolution) | Low (no conflicts) |
Interview Signals¶
Strong answers include:¶
- Understanding offline writes queued locally and synced atomically on reconnect
- Knowing last-write-wins is simple but causes silent data loss
- Aware of vector clocks and how they preserve edit causality
- Understanding CRDTs as solution (append-only, conflict-free by design)
- Aware of Firestore transaction gotcha (offline writes break atomicity)
Weak answers:¶
- Thinking offline writes automatically "merge" (they don't; conflict resolution required)
- Not recognizing silent data loss with LWW (assuming it's fine)
- Unaware of CRDT approach (thinking only option is complex versioning)
- Not considering that both edits might be important (assuming one "wins")
Common Mistakes¶
- Assuming LWW is sufficient: Works until user experiences data loss
- Nested transactions with offline writes: Offline write not included in transaction boundary
- Clock skew: Relying on device time (user sets phone to 1970, breaks timestamp comparison)
- Not logging conflicts: Silent resolution hides bugs; should log when conflict occurs
Design Patterns for Reliable Sync¶
- Server authority: Don't attempt merge; let server decide (simple, may lose data)
- Client merge: Device and cloud negotiate (complex, preserves both)
- CRDTs: Structure data as conflict-free types (hard to retrofit, very safe)
- Pessimistic lock: Prevent concurrent writes (responsive but requires online)
Performance Debug Approach¶
- Firebase Emulator: Test offline/online scenarios locally
- Logcat "Firebase": Track synced writes, conflicts
- Database Rules: Add logging when conflicts detected
- Manual testing: Two devices, offline one device, edit same field, reconnect, verify result
Related Deep Dives¶
- WorkManager & Background Execution - Sync strategy with WorkManager
- Reactive Programming - Observable sync status in Firebase listeners
- Database Query Optimization - Local-first sync with Room