> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ably/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Conflict Resolution

## Understanding Conflicts

In distributed systems with realtime synchronization, conflicts occur when multiple clients or processes attempt to modify the same data concurrently. LiveSync provides mechanisms to detect, prevent, and resolve these conflicts.

## Types of Conflicts

### 1. Concurrent Updates

Multiple users modifying the same resource simultaneously:

```javascript theme={null}
// User A and User B both edit the same document
// User A: Changes title to "Project Alpha"
// User B: Changes title to "Project Beta"
// Conflict: Which title should be kept?
```

### 2. Optimistic Update Conflicts

Client-side optimistic updates that fail server validation:

```javascript theme={null}
// Client optimistically updates task status
model.optimistic({
  mutationId: 'update-123',
  name: 'task.updated',
  data: { status: 'completed' }
});

// Server rejects due to business logic
// (e.g., task has unmet dependencies)
```

### 3. Ordering Conflicts

Events arriving out of order due to network conditions:

```javascript theme={null}
// Events published: Delete -> Update
// Events received: Update -> Delete
// Conflict: Processing order matters
```

### 4. State Divergence

Client state drifting from server state due to missed updates:

```javascript theme={null}
// Client disconnects
// Server processes updates: A, B, C
// Client reconnects but only receives: A, C
// Conflict: Missing event B
```

## Conflict Resolution Strategies

### Last-Write-Wins (LWW)

The most recent update overwrites previous changes:

```javascript theme={null}
function merge(state, event) {
  const existingItem = state.items.find(i => i.id === event.data.id);
  
  if (!existingItem) {
    // New item, add it
    state.items.push(event.data);
  } else {
    // Compare timestamps
    if (event.data.updatedAt > existingItem.updatedAt) {
      // Newer update wins
      Object.assign(existingItem, event.data);
    }
    // Otherwise, ignore older update
  }
  
  return state;
}
```

**Pros:**

* Simple to implement
* No user intervention needed
* Works well for single-field updates

**Cons:**

* Can lose data
* Not suitable for complex objects
* No merge of concurrent changes

### Field-Level Merging

Merge changes at the field level rather than object level:

```javascript theme={null}
function merge(state, event) {
  const item = state.items.find(i => i.id === event.data.id);
  
  if (!item) {
    state.items.push(event.data);
    return state;
  }
  
  // Merge field by field
  Object.keys(event.data.changes).forEach(field => {
    const newValue = event.data.changes[field];
    const currentValue = item[field];
    
    // Compare timestamps per field
    if (!item._timestamps?.[field] || 
        event.data.timestamp > item._timestamps[field]) {
      item[field] = newValue;
      item._timestamps = item._timestamps || {};
      item._timestamps[field] = event.data.timestamp;
    }
  });
  
  return state;
}
```

**Example:**

```javascript theme={null}
// User A updates: { title: "New Title", timestamp: 100 }
// User B updates: { status: "done", timestamp: 101 }
// Result: { title: "New Title", status: "done" }
```

**Pros:**

* Preserves more changes
* Better for multi-field objects
* Reduces data loss

**Cons:**

* More complex implementation
* Requires field-level tracking
* May create inconsistent states

### Operational Transformation (OT)

Transform operations to maintain consistency:

```javascript theme={null}
// Example: Collaborative text editing
function transformOperations(op1, op2) {
  // If op1 inserts "Hello" at position 0
  // And op2 inserts "World" at position 0
  // Transform op2 to insert at position 5 (after "Hello")
  
  if (op1.type === 'insert' && op2.type === 'insert') {
    if (op2.position >= op1.position) {
      op2.position += op1.text.length;
    }
  }
  
  return [op1, op2];
}

function merge(state, event) {
  const transformed = transformAgainstHistory(
    event.data.operations,
    state.operationHistory
  );
  
  applyOperations(state, transformed);
  state.operationHistory.push(transformed);
  
  return state;
}
```

**Pros:**

* Ideal for collaborative editing
* Strong consistency guarantees
* Preserves user intent

**Cons:**

* Complex implementation
* Requires operation history
* Higher computational cost

### Conflict-Free Replicated Data Types (CRDTs)

Use data structures that automatically resolve conflicts:

```javascript theme={null}
import { LWWMap } from 'yjs'; // Example CRDT library

function merge(state, event) {
  // Use CRDT for automatic conflict resolution
  const crdt = new LWWMap(state.data);
  
  event.data.changes.forEach(change => {
    crdt.set(change.key, change.value, change.timestamp);
  });
  
  return {
    ...state,
    data: crdt.toJSON()
  };
}
```

**Common CRDT Types:**

* **LWW-Element-Set**: Last-write-wins for sets
* **OR-Set**: Observed-remove set
* **G-Counter**: Grow-only counter
* **PN-Counter**: Positive-negative counter

**Pros:**

* Automatic conflict resolution
* Strong consistency
* No coordination needed

**Cons:**

* Limited data structure types
* Memory overhead
* Learning curve

### Server-Authoritative Resolution

Let the server be the single source of truth:

```javascript theme={null}
// Client side: Only display confirmed updates
model.subscribe(
  (err, state) => {
    // Only confirmed changes from server
    updateUI(state);
  },
  { optimistic: false } // Disable optimistic updates
);

// Server side: Validate and resolve conflicts
app.post('/api/tasks/:id', async (req, res) => {
  const { id } = req.params;
  const { changes, expectedVersion } = req.body;
  
  await db.transaction(async (tx) => {
    // Lock the row for update
    const task = await tx.query(
      'SELECT * FROM tasks WHERE id = $1 FOR UPDATE',
      [id]
    );
    
    // Check version
    if (task.version !== expectedVersion) {
      throw new Error('Conflict: Task was modified');
    }
    
    // Apply changes and increment version
    await tx.query(`
      UPDATE tasks 
      SET status = $1, version = version + 1
      WHERE id = $2
    `, [changes.status, id]);
    
    // Publish to outbox
    await tx.query(`
      INSERT INTO outbox (channel, name, data)
      VALUES ($1, $2, $3)
    `, [`tasks:${id}`, 'task.updated', JSON.stringify(changes)]);
  });
  
  res.json({ success: true });
});
```

**Pros:**

* Simple client logic
* Server controls business rules
* Clear consistency model

**Cons:**

* Higher latency for updates
* No optimistic updates
* Server bottleneck

## Optimistic Update Patterns

### Optimistic with Rollback

Apply updates immediately, rollback on failure:

```javascript theme={null}
async function updateTask(taskId, status) {
  const mutationId = crypto.randomUUID();
  
  // Apply optimistically
  const [confirmation, cancel] = await model.optimistic({
    mutationId,
    name: 'task.updated',
    data: { id: taskId, status }
  });
  
  try {
    // Send to server
    await fetch(`/api/tasks/${taskId}`, {
      method: 'PATCH',
      body: JSON.stringify({ mutationId, status })
    });
    
    // Wait for confirmation
    await confirmation;
    
  } catch (error) {
    // Rollback on error
    cancel();
    
    // Notify user
    showError('Failed to update task');
  }
}
```

### Optimistic with Reconciliation

Merge optimistic state with confirmed state:

```javascript theme={null}
function merge(state, event) {
  if (event.type === 'confirmed') {
    // Server confirmed update
    const optimisticEvent = state.pendingEvents.find(
      e => e.mutationId === event.mutationId
    );
    
    if (optimisticEvent) {
      // Compare optimistic vs confirmed
      if (JSON.stringify(optimisticEvent.data) !== 
          JSON.stringify(event.data)) {
        // Server made changes, use server version
        console.log('Server modified update:', event.data);
      }
      
      // Remove from pending
      state.pendingEvents = state.pendingEvents.filter(
        e => e.mutationId !== event.mutationId
      );
    }
    
    // Apply confirmed update
    applyUpdate(state, event.data);
    
  } else if (event.type === 'optimistic') {
    // Track pending
    state.pendingEvents.push(event);
    applyUpdate(state, event.data);
  }
  
  return state;
}
```

### Optimistic with Retry

Retry failed optimistic updates:

```javascript theme={null}
async function updateWithRetry(taskId, changes, maxRetries = 3) {
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      const mutationId = crypto.randomUUID();
      
      const [confirmation, cancel] = await model.optimistic({
        mutationId,
        name: 'task.updated',
        data: { id: taskId, ...changes }
      });
      
      await fetch(`/api/tasks/${taskId}`, {
        method: 'PATCH',
        body: JSON.stringify({ mutationId, ...changes })
      });
      
      await confirmation;
      return; // Success
      
    } catch (error) {
      attempt++;
      
      if (attempt >= maxRetries) {
        throw new Error('Max retries exceeded');
      }
      
      // Exponential backoff
      await new Promise(resolve => 
        setTimeout(resolve, Math.pow(2, attempt) * 1000)
      );
    }
  }
}
```

## Version Control Strategies

### Optimistic Locking

Use version numbers to detect conflicts:

```javascript theme={null}
// Client side
async function updateDocument(docId, changes, currentVersion) {
  const response = await fetch(`/api/documents/${docId}`, {
    method: 'PATCH',
    body: JSON.stringify({
      changes,
      expectedVersion: currentVersion
    })
  });
  
  if (response.status === 409) {
    // Conflict detected
    const { latestVersion, latestData } = await response.json();
    
    // Show conflict resolution UI
    showConflictDialog({
      local: changes,
      remote: latestData,
      onResolve: (resolved) => {
        updateDocument(docId, resolved, latestVersion);
      }
    });
  }
}

// Server side
app.patch('/api/documents/:id', async (req, res) => {
  const { id } = req.params;
  const { changes, expectedVersion } = req.body;
  
  await db.transaction(async (tx) => {
    const doc = await tx.query(
      'SELECT * FROM documents WHERE id = $1 FOR UPDATE',
      [id]
    );
    
    if (doc.version !== expectedVersion) {
      // Version mismatch - conflict!
      return res.status(409).json({
        error: 'Conflict',
        latestVersion: doc.version,
        latestData: doc.data
      });
    }
    
    // Update with new version
    await tx.query(`
      UPDATE documents 
      SET data = $1, version = $2
      WHERE id = $3
    `, [changes, expectedVersion + 1, id]);
    
    // Publish to outbox
    await tx.query(`
      INSERT INTO outbox (channel, name, data)
      VALUES ($1, $2, $3)
    `, [`docs:${id}`, 'doc.updated', JSON.stringify(changes)]);
  });
  
  res.json({ success: true });
});
```

### Vector Clocks

Track causality across distributed updates:

```javascript theme={null}
class VectorClock {
  constructor() {
    this.clock = {};
  }
  
  increment(nodeId) {
    this.clock[nodeId] = (this.clock[nodeId] || 0) + 1;
  }
  
  merge(other) {
    Object.keys(other.clock).forEach(nodeId => {
      this.clock[nodeId] = Math.max(
        this.clock[nodeId] || 0,
        other.clock[nodeId]
      );
    });
  }
  
  compare(other) {
    let greater = false;
    let less = false;
    
    const allNodes = new Set([
      ...Object.keys(this.clock),
      ...Object.keys(other.clock)
    ]);
    
    for (const node of allNodes) {
      const thisValue = this.clock[node] || 0;
      const otherValue = other.clock[node] || 0;
      
      if (thisValue > otherValue) greater = true;
      if (thisValue < otherValue) less = true;
    }
    
    if (greater && !less) return 1;   // this > other
    if (less && !greater) return -1;  // this < other
    if (greater && less) return 0;    // concurrent
    return 2;                          // equal
  }
}

function merge(state, event) {
  const eventClock = new VectorClock();
  eventClock.clock = event.vectorClock;
  
  const stateClock = new VectorClock();
  stateClock.clock = state.vectorClock;
  
  const comparison = stateClock.compare(eventClock);
  
  if (comparison === -1) {
    // Event is newer, apply it
    applyUpdate(state, event.data);
    stateClock.merge(eventClock);
    state.vectorClock = stateClock.clock;
  } else if (comparison === 0) {
    // Concurrent updates - conflict!
    handleConflict(state, event);
  }
  // comparison === 1: Event is older, ignore
  
  return state;
}
```

## Conflict Detection

### Monitoring Conflicts

```javascript theme={null}
const conflictMetrics = {
  total: 0,
  resolved: 0,
  manual: 0
};

function merge(state, event) {
  const conflict = detectConflict(state, event);
  
  if (conflict) {
    conflictMetrics.total++;
    
    // Log conflict for analysis
    logConflict({
      type: conflict.type,
      resource: event.data.id,
      timestamp: Date.now(),
      details: conflict.details
    });
    
    // Attempt automatic resolution
    const resolved = resolveConflict(state, event, conflict);
    
    if (resolved) {
      conflictMetrics.resolved++;
      return resolved;
    }
    
    // Require manual resolution
    conflictMetrics.manual++;
    showConflictUI(state, event, conflict);
  }
  
  return state;
}

function detectConflict(state, event) {
  const item = state.items.find(i => i.id === event.data.id);
  
  if (!item) return null;
  
  // Check for concurrent modifications
  if (event.data.version !== item.version + 1) {
    return {
      type: 'version-mismatch',
      expected: item.version + 1,
      received: event.data.version
    };
  }
  
  // Check for pending optimistic updates
  const pending = state.pendingEvents.find(
    e => e.data.id === event.data.id
  );
  
  if (pending) {
    return {
      type: 'optimistic-conflict',
      optimistic: pending.data,
      confirmed: event.data
    };
  }
  
  return null;
}
```

### User Notification

```javascript theme={null}
function showConflictDialog(conflict) {
  const dialog = {
    title: 'Conflicting Changes Detected',
    message: `This ${conflict.resourceType} was modified by another user.`,
    options: [
      {
        label: 'Keep My Changes',
        value: 'local',
        description: 'Overwrite remote changes with yours'
      },
      {
        label: 'Use Their Changes',
        value: 'remote',
        description: 'Discard your changes'
      },
      {
        label: 'Merge Changes',
        value: 'merge',
        description: 'Combine both sets of changes'
      },
      {
        label: 'Review Differences',
        value: 'review',
        description: 'See detailed comparison'
      }
    ],
    onResolve: (choice) => {
      switch (choice) {
        case 'local':
          forceUpdate(conflict.local);
          break;
        case 'remote':
          acceptRemote(conflict.remote);
          break;
        case 'merge':
          showMergeEditor(conflict);
          break;
        case 'review':
          showDiffViewer(conflict);
          break;
      }
    }
  };
  
  showDialog(dialog);
}
```

## Best Practices

### 1. Choose the Right Strategy

```javascript theme={null}
// For simple data: Last-write-wins
const simpleData = {
  status: 'active',
  priority: 'high'
};

// For complex objects: Field-level merging
const complexData = {
  title: 'Project',
  description: 'Long description...',
  metadata: { /* ... */ }
};

// For collaborative editing: Operational Transformation
const collaborativeText = {
  content: 'Document content...',
  operations: [ /* ... */ ]
};
```

### 2. Implement Proper Validation

```javascript theme={null}
app.patch('/api/resources/:id', async (req, res) => {
  const { id } = req.params;
  const { changes, mutationId } = req.body;
  
  try {
    // Validate changes
    const errors = validateChanges(changes);
    if (errors.length > 0) {
      // Reject optimistic update
      await rejectMutation(mutationId, errors);
      return res.status(400).json({ errors });
    }
    
    // Apply changes
    await applyChanges(id, changes, mutationId);
    res.json({ success: true });
    
  } catch (error) {
    await rejectMutation(mutationId, [error.message]);
    res.status(500).json({ error: error.message });
  }
});

async function rejectMutation(mutationId, errors) {
  await db.query(`
    INSERT INTO outbox (mutation_id, channel, name, rejected, data)
    VALUES ($1, $2, $3, true, $4)
  `, [mutationId, 'errors', 'update.rejected', JSON.stringify(errors)]);
}
```

### 3. Test Conflict Scenarios

```javascript theme={null}
describe('Conflict Resolution', () => {
  it('should handle concurrent updates', async () => {
    const model = createModel();
    
    // Simulate concurrent updates
    const update1 = { id: 1, title: 'Title A', timestamp: 100 };
    const update2 = { id: 1, title: 'Title B', timestamp: 101 };
    
    const state1 = merge(model.state, { data: update1 });
    const state2 = merge(state1, { data: update2 });
    
    // Verify resolution
    expect(state2.items[0].title).toBe('Title B'); // Later wins
  });
  
  it('should rollback failed optimistic updates', async () => {
    const model = createModel();
    
    // Apply optimistic update
    const [confirmation, cancel] = await model.optimistic({
      mutationId: 'test-123',
      data: { status: 'done' }
    });
    
    // Simulate rejection
    cancel();
    
    // Verify rollback
    expect(model.state.items[0].status).toBe('pending');
  });
});
```

### 4. Monitor and Analyze

```javascript theme={null}
// Track conflict metrics
const metrics = {
  conflicts: {
    total: 0,
    autoResolved: 0,
    manualResolved: 0,
    byType: {}
  }
};

function recordConflict(type, resolution) {
  metrics.conflicts.total++;
  metrics.conflicts.byType[type] = 
    (metrics.conflicts.byType[type] || 0) + 1;
  
  if (resolution === 'auto') {
    metrics.conflicts.autoResolved++;
  } else if (resolution === 'manual') {
    metrics.conflicts.manualResolved++;
  }
  
  // Send to analytics
  analytics.track('conflict', { type, resolution });
}
```

### 5. Document Behavior

```javascript theme={null}
/**
 * Merges incoming change events with existing state.
 * 
 * Conflict Resolution Strategy:
 * - Uses last-write-wins for simple field updates
 * - Requires manual resolution for complex objects
 * - Automatically rolls back rejected optimistic updates
 * 
 * @param {Object} state - Current model state
 * @param {Object} event - Incoming change event
 * @returns {Object} Updated state
 */
function merge(state, event) {
  // Implementation...
}
```

## Next Steps

<Card title="Database Sync" icon="database" href="/docs/livesync/sync">
  Learn more about database synchronization patterns
</Card>

<Card title="Models SDK" icon="code" href="/docs/livesync/postgres/models">
  Explore the Models SDK for advanced conflict handling
</Card>

<Card title="Quickstart" icon="rocket" href="/docs/livesync/postgres/quickstart">
  Build a realtime application with LiveSync
</Card>
