Data Synchronization and Conflict Handling Fundamentals in React Native
Course: React Native Training
Topic: Data Synchronization and Conflict Handling Fundamentals
Course Link: React Native Training at JustAcademy
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction to Data Synchronization
Data synchronization is the process of keeping data consistent between two or more storage locations, such as a mobile device and a remote server. In React Native applications, synchronization is important when users access their information on multiple devices or work with data while offline.
For example, a user may create a task on a mobile phone while the device has no internet connection. The application stores the task locally and synchronizes it with the backend when connectivity returns. If the user also edits the same task on a tablet, the application must determine how to handle the two versions.
Data synchronization is not simply sending information to a server. A complete synchronization system must consider local storage, network availability, authentication, retries, duplicate requests, data versions, deletions, and conflicts.
Examples of Data Synchronization
- Synchronizing notes between a phone and a tablet.
- Updating shopping cart contents across devices.
- Saving task completion status to a backend.
- Uploading forms collected by field workers while offline.
- Synchronizing user profile information after an edit.
- Keeping application records consistent between SQLite and a server database.
- Downloading new messages and uploading locally created messages.
2. Learning Objectives
After studying this topic, learners should be able to:
- Understand data synchronization and its importance in React Native.
- Differentiate local storage from remote storage.
- Understand online-first and offline-first application architectures.
- Use APIs to upload and download application data.
- Understand synchronization queues and pending changes.
- Identify common synchronization conflicts.
- Compare last-write-wins, field-level merging, and manual conflict resolution.
- Understand version numbers, timestamps, and optimistic concurrency.
- Implement basic retry and duplicate-request protection strategies.
- Design a simple offline-first synchronization workflow.
- Test synchronization failures and recovery scenarios.
3. Understanding Local and Remote Data
React Native applications commonly work with two categories of data storage: local storage on the device and remote storage managed by a backend service.
Local Storage
Local storage keeps data on the user's device. SQLite is useful for structured records, while AsyncStorage can store small, non-sensitive key-value preferences. Local data allows supported application features to continue working without an internet connection.
Remote Storage
Remote storage keeps data on a server or cloud database. An application usually accesses this data through an API. Remote storage can make information available across devices and users, subject to authentication and authorization rules.
| Feature |
Local Storage |
Remote Storage |
| Location |
User's device |
Server or cloud service |
| Internet requirement |
Not required for local operations |
Usually required to communicate with the server |
| Speed |
Often faster for local reads and writes |
Depends on network latency and server performance |
| Cross-device access |
Not automatic |
Possible through authenticated accounts and APIs |
| Offline support |
Can continue using saved data |
Unavailable during disconnection unless a local copy exists |
| Consistency |
May become outdated compared with server data |
Can act as the authoritative source when designed that way |
4. What Is Data Synchronization?
Data synchronization updates one data source based on changes in another source. The goal is to make relevant data consistent according to the application's synchronization rules.
Synchronization may involve downloading new records, uploading locally created records, updating existing records, propagating deletions, or reconciling conflicting versions.
Basic Synchronization Example
- A user creates a task on a mobile device.
- The application saves the task in its local SQLite database.
- The application records that the task has not yet synchronized.
- When connectivity becomes available, the application sends the pending change to the backend.
- The backend validates and saves the change.
- The application receives the confirmed server record and updates the local synchronization status.
This approach allows the application to respond immediately to the user while coordinating with the server in the background or when the application is active.
5. Online-First vs Offline-First Architecture
Applications differ in how they behave when the internet is unavailable. The appropriate architecture depends on whether the user must continue working offline and how important local changes are.
| Architecture |
How It Works |
Suitable Example |
| Online-first |
Requests the server for most operations and may block or limit changes when offline. |
Applications where current server information is essential. |
| Offline-capable |
Supports selected functions using cached or locally stored information. |
Reading previously downloaded articles. |
| Offline-first |
Uses the local data source for normal reads and often writes, then synchronizes with the server. |
Notes, task management, field data collection. |
Offline-first applications need a reliable strategy for tracking pending writes and resolving differences between local and remote records.
6. Common Data Synchronization Strategies
6.1 Full Synchronization
Full synchronization downloads or compares a large portion of the dataset. It can be simple to implement for small datasets, but may consume excessive bandwidth and time as the dataset grows.
6.2 Incremental Synchronization
Incremental synchronization transfers only records or changes that have occurred since a previous synchronization point. It reduces unnecessary data transfer and is commonly used for larger datasets.
6.3 Push Synchronization
Push synchronization sends local changes to the server. For example, when a user edits a task, the application queues the change and uploads it when possible.
6.4 Pull Synchronization
Pull synchronization downloads changes from the server. The application may request records updated since its last successful synchronization.
6.5 Bidirectional Synchronization
Bidirectional synchronization uploads local changes and downloads remote changes. It must also reconcile conflicts when both sides have changed the same record.
| Strategy |
Main Purpose |
Consideration |
| Full sync |
Refresh a large dataset |
Can transfer unnecessary data |
| Incremental sync |
Transfer only changes |
Requires reliable change tracking |
| Push |
Upload local changes |
Must handle retries and duplicate submissions |
| Pull |
Retrieve server changes |
Must identify which records are new or changed |
| Bidirectional |
Exchange changes in both directions |
Requires a conflict-resolution policy |
7. Understanding Data Conflicts
A data conflict occurs when two or more changes cannot safely be applied together under the application's rules. This often happens when a user edits the same record on multiple devices while the devices are disconnected or working with outdated data.
Example of a Conflict
Suppose a task has the title Prepare project report.
- Device A goes offline and changes the title to
Prepare client report.
- Device B remains online and changes the title to
Prepare monthly report.
- Both devices now have different versions of the same task.
- When Device A reconnects, its change conflicts with the server's version.
The application must decide whether one version should replace the other, whether both changes can be merged, or whether the user should choose the correct result.
8. Types of Synchronization Conflicts
8.1 Update-Update Conflict
Two clients update the same record before receiving each other's changes.
8.2 Update-Delete Conflict
One client edits a record while another client deletes it. The system needs a rule for whether the edit can restore the record or the deletion takes precedence.
8.3 Create-Create Conflict
Two clients create what they believe is the same logical record. Unique identifiers and server-side uniqueness constraints help prevent accidental duplication.
8.4 Duplicate Submission
A client retries a request after a timeout, even though the server may already have processed the original request. Without duplicate protection, the same record or transaction may be created twice.
8.5 Stale Data Conflict
A client sends an update based on an old version of a record. The server must determine whether to accept the update or reject it because newer data already exists.
9. Conflict Resolution Fundamentals
Conflict resolution defines how the application handles competing changes. The correct strategy depends on the data, business requirements, and the consequences of losing or merging information.
A personal theme preference may be safe to resolve automatically. A shared financial record or collaborative document may require stronger safeguards.
10. Last-Write-Wins Strategy
Last-write-wins (LWW) selects one version based on an ordering rule, often a timestamp or server-managed version. The winning version replaces the losing version.
Example
| Version |
Updated Time |
Title |
| Device A |
10:05 |
Prepare client report |
| Device B |
10:08 |
Prepare monthly report |
If the system's trusted ordering rule identifies Device B's change as newer, its title becomes the saved result.
Advantages
- Relatively simple to implement.
- Can work well for low-risk settings and independent personal records.
- Requires limited user interaction.
Disadvantages
- The losing change may be discarded.
- Device clocks may be incorrect or inconsistent.
- A later timestamp does not necessarily mean a change is more correct.
Best practice: Avoid relying exclusively on client device clocks for important conflict decisions. Prefer server-managed versions or another deterministic ordering mechanism when appropriate.
11. Field-Level Merge Strategy
Field-level merging combines changes to different fields of the same record. It can preserve more user work than replacing an entire record with one version.
Example
Suppose a task contains a title and a priority.
- Device A changes the title to
Prepare final report.
- Device B changes the priority from
Normal to High.
- Because different fields changed, the application may keep both updates.
| Field |
Original Value |
Device A |
Device B |
Possible Merged Value |
| Title |
Prepare report |
Prepare final report |
Prepare report |
Prepare final report |
| Priority |
Normal |
Normal |
High |
High |
When both devices change the same field to different values, the application still needs a rule for that field. Field-level merging works best when the system can track the original version and identify which fields changed.
12. Manual Conflict Resolution
Manual conflict resolution presents the competing versions to the user and asks them to choose or combine the correct result. It is useful when silently discarding data could cause serious problems.
Example User Interface
A document editor might display:
- Local version: The content saved on this device.
- Server version: The latest version stored on the server.
- Actions: Keep local version, keep server version, or review and merge.
The application should preserve both versions until the user's decision is safely saved. Avoid overwriting either version before the conflict has been resolved.
13. Version-Based Conflict Detection
Version-based conflict detection allows the server to identify when a client is editing an outdated record. Each record can include a version number that changes whenever an accepted update is made.
Example Record
{
"id": "task-101",
"title": "Prepare report",
"version": 4
}
The client retrieves version 4. Another client updates the record, and the server stores version 5. If the first client submits an update based on version 4, the server can reject it and return the latest version for reconciliation.
Advantages of Version Numbers
- Detects stale updates without relying solely on client timestamps.
- Makes concurrent updates easier to identify.
- Allows the client to fetch the latest record and attempt a merge.
- Supports explicit conflict handling and auditing.
14. Optimistic Concurrency Control
Optimistic concurrency control assumes that conflicting edits are relatively uncommon. The client submits an update together with the version it originally retrieved. The server accepts the update only if the record has not changed since that version.
For HTTP APIs, this can be implemented with a version field or mechanisms such as ETags and the If-Match request header.
Example HTTP Request
PATCH /api/tasks/task-101
If-Match: "version-4"
Content-Type: application/json
{
"title": "Prepare final report"
}
The server compares the supplied version with the current record version. If they match, it can accept the update and issue a new version. If they do not match, the server should reject the stale update and return enough information for the client to reconcile the conflict. Depending on the API design, a stale update may return HTTP 412 or HTTP 409.
15. Designing a Synchronization Workflow
A robust synchronization workflow separates local editing, change tracking, network communication, server validation, and conflict resolution.
- Read local data: Display the most recently saved local records.
- Apply the user change: Update the local database so the interface can respond immediately.
- Track the change: Mark the record as pending synchronization or add an operation to a durable queue.
- Check connectivity: Attempt synchronization when the application can reach the server.
- Upload changes: Send pending operations with identifiers and version information.
- Validate on the server: Check authentication, authorization, data constraints, and record versions.
- Resolve conflicts: Merge, reject, or select a winning version according to the defined policy.
- Apply server results: Update the local record with the accepted server version.
- Mark completion: Remove or acknowledge successfully synchronized operations.
- Retry failures: Preserve pending operations until they succeed or require user action.
16. Data Synchronization Architecture in React Native
A common offline-first architecture has a UI layer, local database, synchronization service, network layer, and backend API.
| Component |
Responsibility |
| React Native UI |
Displays data and accepts user actions. |
| State management |
Keeps the visible interface consistent with local data. |
| Local database |
Stores records for immediate access and offline work. |
| Pending change queue |
Tracks operations that have not been confirmed by the server. |
| Synchronization service |
Coordinates upload, download, retries, and reconciliation. |
| Network layer |
Sends requests and handles responses or connectivity errors. |
| Backend API |
Authenticates requests, validates changes, and manages authoritative records. |
17. Tracking Pending Changes in SQLite
An offline-first application can store a synchronization status on each record. This helps distinguish records that have been confirmed by the server from records that still need to be uploaded.
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
completed INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL,
sync_status TEXT NOT NULL DEFAULT 'pending',
server_version INTEGER
);
Possible values for sync_status include pending, synced, and failed. Define their meanings clearly. A failed operation may be retried automatically, require authentication, or need user intervention depending on the error.
Reading Pending Records
const pendingTasks = await db.getAllAsync(
`SELECT id, title, completed, updated_at, server_version
FROM tasks
WHERE sync_status = ?
ORDER BY updated_at ASC`,
'pending'
);
This query returns records awaiting synchronization. In a real application, an operation queue may be preferable when updates, deletions, or multiple changes to the same record need to be tracked separately.
18. Using a Durable Outbox Queue
An outbox is a persistent queue of operations that must be delivered to the server. It is particularly useful when the application must retain offline changes even if it is closed or the device restarts.
Example Outbox Table
CREATE TABLE IF NOT EXISTS sync_outbox (
id TEXT PRIMARY KEY,
entity_type TEXT NOT NULL,
entity_id TEXT NOT NULL,
operation TEXT NOT NULL,
payload TEXT NOT NULL,
created_at TEXT NOT NULL,
retry_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT
);
When the user makes a change, the application can update its local data and enqueue the corresponding operation in a single SQLite transaction. The synchronization worker later reads pending operations and sends them to the server.
A durable queue is more reliable than keeping pending changes only in JavaScript memory, because in-memory state disappears when the application process is terminated.
19. Detecting Network Connectivity
Connectivity monitoring can help the application decide when to attempt synchronization. A commonly used React Native package is @react-native-community/netinfo.
Installation
npm install @react-native-community/netinfo
For Expo projects, use the package installation method recommended for the project's SDK.
Example Connectivity Listener
import NetInfo from '@react-native-community/netinfo';
const unsubscribe = NetInfo.addEventListener((state) => {
const connected = Boolean(
state.isConnected && state.isInternetReachable !== false
);
if (connected) {
console.log('Network may be available; consider syncing.');
} else {
console.log('Offline; keep local changes queued.');
}
});
// Call unsubscribe() when the listener is no longer needed.
Network status is only a signal, not proof that the backend is reachable or that a request will succeed. DNS errors, server outages, expired credentials, and unstable connections can still prevent synchronization. Always handle request failures.
20. Uploading Changes to an API
A synchronization service typically sends pending local changes to an authenticated backend API. The API should validate the operation and return the accepted record or a conflict response.
async function uploadTask(task, accessToken) {
const response = await fetch('https://api.example.com/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
},
body: JSON.stringify(task)
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
return await response.json();
}
The URL is an illustrative placeholder and must be replaced with your backend endpoint. This simple example demonstrates an upload request, not a complete synchronization engine. A production implementation must handle authentication refresh, retries, duplicate requests, conflict responses, and safe queue acknowledgment.
21. Downloading Changes from the Server
The application must also retrieve changes made elsewhere. Incremental APIs commonly accept a cursor, version, or timestamp to request records changed since the last successful synchronization.
async function fetchUpdatedTasks(accessToken, cursor) {
const url = new URL('https://api.example.com/tasks/changes');
if (cursor) {
url.searchParams.set('cursor', cursor);
}
const response = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error(`Download failed: ${response.status}`);
}
return await response.json();
}
The server should return a reliable next cursor or checkpoint. Store that checkpoint only after the corresponding changes have been safely applied locally. Otherwise, the client could skip records if it advances the cursor before saving the downloaded data.
22. Preventing Duplicate Requests
A request may reach the server even when the client does not receive its response. The client may retry, potentially creating duplicate records or repeating a non-idempotent operation.
An idempotency key is a unique identifier assigned to a logical operation. The backend records the result associated with the key and returns the same logical result when it receives a retry using that key.
const operationId = 'operation-unique-id';
const response = await fetch('https://api.example.com/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': operationId
},
body: JSON.stringify({
id: 'task-101',
title: 'Prepare report'
})
});
This code illustrates the request shape only. The backend must implement idempotency-key storage and duplicate detection. Generate a unique, persistent key for each logical operation and reuse that same key for retries of that operation.
23. Retry Mechanisms and Exponential Backoff
Temporary failures can be retried, but repeated immediate requests may overload the server or drain the device battery. Exponential backoff increases the delay between retry attempts.
For example, a client might wait approximately 1 second before the first retry, 2 seconds before the next, and 4 seconds before another. A maximum delay and random jitter help prevent many clients from retrying simultaneously.
Basic Delay Calculation
function getRetryDelay(attempt) {
const baseDelay = 1000;
const maxDelay = 60000;
const exponentialDelay = Math.min(
maxDelay,
baseDelay * (2 ** attempt)
);
const jitter = Math.random() * 500;
return exponentialDelay + jitter;
}
This helper calculates a delay; a complete retry scheduler must also enforce a retry limit or escalation policy, honor server-provided retry guidance where appropriate, persist retry state when needed, and distinguish temporary network errors from permanent validation or authorization errors.
24. Handling HTTP Errors During Synchronization
| Status or Failure |
Possible Meaning |
Suggested Handling |
| Network error or timeout |
Server could not be reached or response was lost |
Keep the operation pending and retry later. |
| 400 Bad Request |
Request data is invalid |
Inspect the payload and report or correct the invalid data. |
| 401 Unauthorized |
Authentication is missing or expired |
Refresh credentials or ask the user to sign in again. |
| 403 Forbidden |
The user is not allowed to perform the operation |
Do not retry blindly; handle authorization and user access. |
| 404 Not Found |
Target record or endpoint is unavailable |
Determine whether the record was deleted, moved, or the request is incorrect. |
| 409 Conflict |
The server detects a conflicting state |
Fetch the latest record and apply the conflict policy. |
| 412 Precondition Failed |
A version or ETag condition was not met |
Retrieve the latest version and reconcile the update. |
| 429 Too Many Requests |
Rate limit exceeded |
Reduce request frequency and respect retry guidance. |
| 5xx Server Error |
Server-side failure |
Retry with backoff when the error is temporary. |
These are common patterns, not universal rules. Follow the API's documented error contract and avoid retrying permanent errors indefinitely.
25. Applying Server Changes to SQLite
When a synchronization response arrives, the application should update the local database to reflect the accepted server state. For multiple related changes, use a transaction so that the data and synchronization checkpoint remain consistent.
async function saveServerTask(db, task) {
await db.runAsync(
`INSERT INTO tasks (
id, title, completed, updated_at, sync_status, server_version
)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
title = excluded.title,
completed = excluded.completed,
updated_at = excluded.updated_at,
sync_status = excluded.sync_status,
server_version = excluded.server_version`,
task.id,
task.title,
task.completed ? 1 : 0,
task.updated_at,
'synced',
task.version
);
}
This example assumes the local schema uses the listed columns and that the server response has been validated. Do not blindly replace local records with server records when local pending changes exist; first determine whether the changes conflict.
26. Deletion Synchronization and Tombstones
Deleting a record locally while offline creates a synchronization challenge. If the record is simply removed from local storage, the application may no longer know that a deletion must be sent to the server.
A tombstone is a marker that records a deletion until the deletion has been synchronized. The marker can contain the record ID, deletion timestamp, and synchronization status.
Example Tombstone Table
CREATE TABLE IF NOT EXISTS deleted_records (
record_id TEXT PRIMARY KEY,
deleted_at TEXT NOT NULL,
sync_status TEXT NOT NULL DEFAULT 'pending'
);
When a user deletes a record offline, the application can save a tombstone and enqueue the deletion. Once the backend confirms the deletion, the local tombstone can be removed according to the synchronization design.
Without deletion tracking, a later download may restore a record that the user had deleted.
27. Handling Concurrent Changes to the Same Record
When the server detects that a record has changed since the client last retrieved it, the client should not automatically overwrite the latest version without a defined policy.
Recommended Reconciliation Flow
- Submit the local change with the version originally retrieved.
- Receive a conflict response if the server version has changed.
- Retrieve the latest server record.
- Compare the local changes with the server changes.
- Apply an automatic merge if the fields can safely be combined.
- Otherwise, preserve both versions and ask the user to resolve the conflict.
- Submit the resolved change against the latest server version.
- Update local data only after receiving the authoritative result.
28. A Simple Conflict-Resolution Helper
The following example demonstrates a basic rule for deciding which version to select. It is suitable only as an educational illustration for low-risk records with a trustworthy ordering value. It is not a complete synchronization algorithm.
function chooseNewerVersion(localRecord, serverRecord) {
if (!localRecord) return serverRecord;
if (!serverRecord) return localRecord;
if (localRecord.version === serverRecord.version) {
return serverRecord;
}
if (localRecord.updatedAt > serverRecord.updatedAt) {
return localRecord;
}
return serverRecord;
}
Comparing client timestamps alone is unsafe when device clocks differ. In a real application, prefer a server-managed version, explicit conflict response, or another well-defined ordering mechanism. Equal timestamps or versions do not always prove that the contents are identical, so the system may need to compare values or hashes as well.
29. Conflict Resolution Decision Table
| Situation |
Possible Strategy |
Reason |
| Personal theme or sort preference |
Last-write-wins |
Low-risk choice that is easy to change again. |
| Different fields changed on the same task |
Field-level merge |
Can preserve both independent changes. |
| Two users edit the same paragraph |
Manual merge or collaboration-aware algorithm |
Automatic replacement may lose meaningful content. |
| Financial transaction or critical business record |
Server validation and explicit conflict handling |
Incorrect automatic merges can have serious consequences. |
| Record deleted on one device and edited on another |
Defined delete-vs-update policy |
Prevents unexpected resurrection or silent loss. |
| Collaborative data with frequent concurrent changes |
Consider a CRDT or specialized synchronization engine |
May allow deterministic merging under a carefully designed data model. |
30. Security and Privacy in Data Synchronization
- Authenticate requests using the application's approved authentication mechanism.
- Enforce authorization on the backend for every record and operation.
- Do not trust user IDs or ownership fields supplied by the client without validation.
- Validate incoming data before applying it to the database.
- Use HTTPS for network communication.
- Do not log access tokens, passwords, or sensitive record contents.
- Protect sensitive local data according to the application's threat model.
- Ensure that data from one account is not synchronized into another account's local workspace.
- Define what happens to pending operations when a user signs out.
- Consider data retention, account deletion, and privacy requirements.
Client-side conflict handling improves consistency but is not a security boundary. The backend must enforce data ownership, access permissions, and business rules.
31. Testing Data Synchronization
Synchronization should be tested under realistic network conditions, including disconnections, retries, concurrent edits, and application restarts.
Important Test Scenarios
- Create a record while online and verify that it appears on another device.
- Create a record offline and verify that it uploads after connectivity returns.
- Edit the same record on two devices and verify the conflict policy.
- Turn off the network during an upload and confirm that the operation remains pending.
- Simulate a timeout after the server processes a request and verify duplicate protection.
- Expire the user's authentication and verify that the application handles the error safely.
- Delete a record offline and confirm that it is not restored unexpectedly.
- Close and reopen the application while operations are pending.
- Verify that incremental synchronization does not skip changes.
- Test large datasets and monitor memory, battery use, and network traffic.
32. Common Mistakes and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Local changes disappear |
Changes were kept only in memory |
Persist local changes and pending operations before attempting network delivery. |
| Records are duplicated |
Retries create a new record each time |
Use stable operation IDs and backend idempotency support. |
| Old data overwrites new data |
No version checking or conflict policy |
Use version-based concurrency control and reconciliation. |
| Changes never synchronize |
Queue processing is not triggered or failures are not retried |
Check network triggers, queue state, retry logic, and API errors. |
| Deleted records return |
Deletion was not recorded for synchronization |
Use tombstones or another reliable deletion-propagation mechanism. |
| Synchronization loops indefinitely |
Server responses are re-enqueued as new local changes |
Distinguish remote updates from user-originated local mutations. |
| Some updates are skipped |
Checkpoint advanced before local data was saved |
Apply downloaded changes and persist the checkpoint consistently. |
| Too many requests are sent |
Every UI change triggers an immediate network call |
Use a queue, batching, debouncing where appropriate, and backoff. |
| Wrong account's data appears |
Local data or queues are not scoped to the signed-in user |
Scope records and pending operations by account and enforce server authorization. |
33. Practical Project: Offline-First Task Synchronization App
Project objective: Build a React Native task manager that saves tasks locally, synchronizes changes with a backend, and handles conflicting edits.
Project Features
- Create, edit, and delete tasks offline.
- Save tasks in a local SQLite database.
- Track pending changes in a durable outbox.
- Detect network availability and trigger synchronization attempts.
- Upload pending changes through a REST API.
- Download remote changes using an incremental synchronization cursor.
- Handle retries with exponential backoff.
- Prevent duplicate submissions with idempotency keys.
- Detect stale updates using server-managed versions.
- Resolve simple conflicts automatically and present complex conflicts to the user.
- Display synchronization status for each task.
Suggested Project Structure
OfflineSyncApp/
├── App.js
├── src/
│ ├── database/
│ │ ├── database.js
│ │ └── taskRepository.js
│ ├── services/
│ │ ├── apiClient.js
│ │ └── syncService.js
│ ├── sync/
│ │ ├── outbox.js
│ │ ├── conflictResolver.js
│ │ └── retryPolicy.js
│ ├── screens/
│ │ ├── TasksScreen.js
│ │ └── ConflictScreen.js
│ └── components/
│ ├── TaskItem.js
│ └── SyncStatus.js
Development Steps
- Create a React Native project and configure local SQLite storage.
- Define a task schema that includes an ID, fields, server version, and synchronization status.
- Build local CRUD operations and make the UI read from local data.
- Create a durable outbox for pending operations.
- Implement API functions for uploading changes and downloading remote updates.
- Add connectivity monitoring and safe retry behavior.
- Implement idempotency and version-based conflict detection on the backend.
- Define how to resolve update-update and update-delete conflicts.
- Add synchronization status and error messages to the interface.
- Test airplane mode, interrupted requests, duplicate retries, and concurrent edits.
- Document the synchronization rules and prepare the project for your portfolio.
Expected Result
The completed application should allow users to work with locally saved tasks while offline, synchronize pending changes when the server is reachable, and handle conflicts according to a documented policy without silently losing important user data.
34. Exercises and Practice Questions
- Explain data synchronization and why it is useful in mobile applications.
- Compare online-first and offline-first architectures.
- Describe full, incremental, push, pull, and bidirectional synchronization.
- Explain how two devices can create a conflict while offline.
- Compare last-write-wins and field-level merging.
- Explain why client timestamps can be unreliable for conflict resolution.
- Describe version-based concurrency control.
- Explain the purpose of an outbox queue.
- Write a sample request that uploads a pending task.
- Explain how idempotency keys prevent duplicate operations.
- Describe exponential backoff and why jitter is useful.
- Explain how a tombstone helps synchronize deletions.
- Design a conflict-resolution flow for a shared notes application.
- Explain why a synchronization cursor should not advance before changes are safely applied.
- Build a simple offline-first task manager and test conflicting edits on two devices.
35. Interview Questions and Answers
Q1. What is data synchronization?
Answer: Data synchronization is the process of exchanging changes between data sources so they remain consistent according to the application's rules.
Q2. What is an offline-first application?
Answer: An offline-first application uses local data as a primary source for supported operations and synchronizes changes with a remote service when connectivity is available.
Q3. What is a synchronization conflict?
Answer: A synchronization conflict occurs when competing changes cannot be applied together without a decision about which values should be preserved.
Q4. What is last-write-wins?
Answer: Last-write-wins selects a version according to a defined ordering rule, often a timestamp or server-managed version. It is simple but can discard the losing change.
Q5. What is field-level merging?
Answer: Field-level merging combines changes to different fields of a record and applies a separate policy when the same field has conflicting values.
Q6. What is optimistic concurrency control?
Answer: It checks whether a record has changed since the client read it and rejects an update based on a stale version.
Q7. What is an outbox queue?
Answer: An outbox is a durable queue that stores operations awaiting delivery to the server, allowing the application to retry after interruptions.
Q8. Why are idempotency keys useful?
Answer: They allow the backend to recognize retries of the same logical operation and prevent duplicate side effects when a request is repeated.
Q9. What is exponential backoff?
Answer: Exponential backoff increases the delay between retry attempts, helping reduce repeated load during temporary failures.
Q10. What is a tombstone?
Answer: A tombstone is a marker that records a deletion until the deletion has been propagated to the other data sources.
Q11. How should an application handle an HTTP 409 response?
Answer: Follow the API contract. A common approach is to retrieve the current server version, reconcile the changes, and resubmit a resolved update if appropriate.
Q12. Why is a local database useful for synchronization?
Answer: It stores records and pending changes persistently, allowing the user to continue supported operations offline and retry synchronization later.
Q13. What is incremental synchronization?
Answer: It transfers changes since a previously saved checkpoint rather than downloading the entire dataset every time.
Q14. Why should server changes be validated?
Answer: Validation protects data integrity and ensures that the user is authorized to perform the requested operation.
Q15. When should manual conflict resolution be used?
Answer: It is useful when competing changes are meaningful and an automatic merge could lose important information or produce an incorrect result.
36. Quick Revision Checklist
- Understand local and remote data sources.
- Know the differences between online-first and offline-first architecture.
- Understand full, incremental, push, pull, and bidirectional synchronization.
- Identify update, delete, create, and duplicate-submission conflicts.
- Compare last-write-wins, field-level merging, and manual resolution.
- Understand server versions and optimistic concurrency control.
- Persist pending operations in a durable queue.
- Use stable IDs and idempotency support to avoid duplicate operations.
- Retry temporary failures with backoff and jitter.
- Track deletions with tombstones or an equivalent strategy.
- Validate server responses and enforce authorization on the backend.
- Test offline operation, retries, concurrent edits, and account switching.
37. Conclusion
Data synchronization and conflict handling are essential when React Native applications must support offline work, multiple devices, or shared remote data. A reliable design combines persistent local storage, a durable pending-change queue, network-aware synchronization, API validation, retry mechanisms, and a clear conflict-resolution policy.
Last-write-wins can be suitable for low-risk settings, while field-level merging can preserve independent changes. Version-based concurrency control and manual resolution are important when changes must not be silently overwritten. For complex collaborative applications, specialized synchronization engines or conflict-free replicated data types may be appropriate.
The most important principle is to define synchronization rules before implementing them. Test interrupted requests, duplicate submissions, concurrent updates, and deletions to ensure that the application preserves user data and behaves predictably.
38. Additional Learning Resources