Published OnJune 24, 2025June 24, 2025

How to Build Robust Offline-First Apps: A Technical Guide to Conflict Resolution with CRDTs and Ditto

We’ll walk through real-world scenarios to show you how developers use Ditto to architect offline-first applications and explore how Ditto’s conflict-friendly, CRDT-based sync model can capture, merge, and audit concurrent data changes directly at the application layer without brittle server logic or custom sync code.

The Offline Conflict Challenge

Modern mobile applications for the deskless workforce, from maintenance task management to retail point-of-sale (POS) systems, need to work offline. The mobile nature of this type of worker makes connectivity unpredictable. This means that multiple devices can update the same data concurrently without a central server mediating. The result? Conflict resolution becomes a critical challenge. In traditional systems, developers might try to enforce a single source of truth (e.g. using a server or leader election through consensus) to avoid conflicts. With custom server-side conflict code, the logic runs on a central device and simply picks a winner or merges behind the scenes. This introduces a problem: the application might not see that a conflict ever occurred. This approach introduces complexity, brittle logic, and poor observability of what actually happened during a conflict. 

In this post, we’ll explore an alternative approach used in Ditto. By designing a conflict-free data model and leveraging property-based sync, we turn this offline conflict problem on its head. We’ll walk through two real-world scenarios, uncover misconceptions in the initial approaches, and show how a new perspective yields a more robust and transparent solution.

Misconception: Forcing a Single Leader for Consistency

A common misconception in offline systems is that you must prevent conflicts at all costs by funneling updates through a single “leader”, either a server or an edge device chosen by consensus. This approach stems from an instinct to preserve consistency by avoiding conflicting data altogether. This comes up with use cases such as serializing payment transactions across point of sale devices, order state reconciliation for kitchen display systems, and hierarchical scenarios like manager and employee. 

The problem with this strategy is twofold: 

(1) In offline scenarios, you inevitably sacrifice some consistency for availability. In fact, offline-first apps explicitly favor availability. Each device must keep working and accept writes even when disconnected and thus must assume that conflicts will happen.

(2) Forcing a single leader (i.e., a server) to choose a winner can hide what really occurred and make debugging difficult. A “last write wins” approach will cause one update to completely overwrite another, potentially losing data (e.g. an order void event could overwrite a payment event or vice versa). 

In our running examples, the misunderstanding was thinking that strict server or leader-driven conflict resolution would provide correctness. In reality, these tactics made the system complex and brittle, without truly reflecting the realities of an offline environment.

Reality Check: Embracing Conflicts for Better Outcomes

When we examine how these systems actually operate in the field, a different picture emerges. Let’s visit the three use cases to walk through step by step:

Payments in a Restaurant

  • Use case: Each register needs to process payments independently when offline. In a busy restaurant, it’s more important that each device can take orders and payments (availability) than to block everyone until a single leader device says it’s okay (which could compromise availability). This is a classic example of trading off strict consistency for availability in a partitioned network. 
  • Implementation: Embrace the reality that there will be some duplicate payments for the same order, which we need to record and reconcile later. The business can refund duplicate payments after the fact. This means that our database needs to record duplicate payments in a clear, auditable way. In other words, the system and business processes together can be built to tolerate eventual consistency
  • Key insight: As long as duplicates are infrequent and handled downstream, the user experience remains smooth. Trying to enforce perfect global consistency is unnecessary as the business can operate with “at least once” payment recording and then reconcile. In fact, attempting leader election would add a single point of failure; if the “leader” tablet goes down, all others might be stuck. No single leader means higher overall uptime for the store.

Front & Back-of-house Order States

  • Use case: Orders have a status that represents discrete states (for example, an order could move through “PAID”, “VOIDED”, “FULFILLED”, etc.). In an offline scenario, two clerks on different devices might perform conflicting actions on the same order.One might void the order while another registers a payment. Modeling this as one status field causes confusion and data loss: was the order voided or paid? 
  • Implementation: With a single status field, these concurrent updates result in a conflict where one action wins and the other is lost (depending on whose update arrived last when syncing). Rather than contorting the system to avoid any conflict, we can embrace conflicts by capturing each device’s intent in the data model. If two actions conflict, record both and resolve the meaning at the application level.
  • Key insight: Conflicts aren’t failures,they’re information. This mindset shift from preventing concurrency to designing for concurrency is key to building an offline-ready architecture. Without special handling, you’d only see one outcome and have no record of the other. A mistake is modeling these states as mutually exclusive in one field, assuming a last-writer-wins merge would somehow sort it out.

Seniority & Hierarchical Overrides 

  • Use case: Imagine two roles: a junior technician and a senior supervisor in a maintenance app. Both can edit tasks, but if a senior and junior edit the same task concurrently, we want the senior’s changes to win (a business rule). How can we enforce that without a server deciding the winner?
  • Implementation: Model an override mechanism. For example, every task has a base record that anyone can edit. In addition, we have an “overrides” collection: if a senior user edits a task, instead of editing the base record directly, the app creates an override document that represents the senior’s version. Both the base and override might sync and exist concurrently, but the app can be written such that when displaying tasks, it checks: “if an override exists for this task, show the override version and ignore the base one.” The junior’s changes go to the base collection, the senior’s to the overrides collection.  In the UI or query logic, you’d merge these by giving precedence to the override if present. 
  • Key insight: This is an application-level conflict resolution. No magical server logic needed. The app applies a business rule by choosing the override data. This way, the junior’s work isn’t lost; it’s just overshadowed for display. If needed, you could log that a junior tried to edit but was overridden. And importantly, if the senior hadn’t made a change, the junior’s edit stands, meaning we don’t pay any coordination cost when there’s no conflict. This pattern of using separate documents or fields for special cases can handle a variety of custom merge logic (priorities, roles, etc.) without breaking the CRDT (more on this later) model. Ditto’s sync ensures all pieces arrive, and your code glues them together appropriately.

Why doesn't everyone do it this way? 

Early smartphones and rugged handhelds shipped with single-core CPUs, <1 GB RAM, and slow flash storage. Battery budgets were so tight that even modest local processing hurt user experience. As a result, most mobile frameworks treated the device as a smart web view.” All heavy-weight logic,validation, deduplication, conflict resolution,ran on the server

Now, a mid-range device in 2025 delivers orders-of-magnitude more compute, dedicated ML accelerators, and GBs of storage. Edge-oriented chips explicitly target running inference and complex business logic locally. Those resources make it cheap to keep a richer state on-device (e.g., an entire payment log) and run the UI logic that detects duplicates or contradictory flags in real time.

For decades, enterprise developers internalised ACID, serialisable isolation, and “single source of truth” as non-negotiable. When mobile arrived, the easiest way to stay in that comfort zone was to keep strong consistency on the server and treat the client as a cache. Then, in the early 2010s, academia discovered Conflict-Free Replicated Data Types (CRDTs). Subsequently, widely cited production libraries (Automerge, Yjs, Collabs, ElectricSQL, Ditto, etc.) appeared years later and are still maturing. https://crdt.tech/papers.html. Until these libraries hardened, “roll your own” merge algorithms were hard to get right and easy to slow down. Therefore, enterprises have largely waited to see how the ecosystem has evolved and are now evaluating whether or not to adopt the latest technologies.

Benefits: Turning Conflicts into a Feature

By adopting these conflict-friendly data models, the system gains numerous benefits:

Business Rules Live Where User Context Exists

The tablet that just swiped a credit card knows the cashier, the shift, and the customer standing there. Because of this, it’s the perfect place to decide whether two $45.67 payments are duplicates and prompt an immediate refund.

Separation of Concerns is Clearer

You write clearer, domain-focused code once, in the client. It’s also easier to test; you can simulate two updates and ensure your UI or logic reacts appropriately.

  • Sync engine (Ditto) ⇒ deterministic, mechanical merging. Takes care of reliably propagating data and merging concurrent edits in a generic way (per field or per document). 
  • UI layer ⇒ domain semantics (“senior tech overrides junior”, “void beats pay”). The application code, which is where business knowledge resides, handles the business rules when those merged results might violate invariants (e.g., “paid and voided” or duplicate transactions). 
Failure Modes are Safer

If the UI logic crashes, the raw facts (both payments, both state flags) are still preserved in the CRDT; nothing is silently discarded by buggy server functions.

Ship Fast and Iterate

Changing a duplicate-detection threshold or override policy is a UI update, not a database migration.

Offline Resilience

Lack of a “lock” mechanism means higher uptime, which is critical in environments like restaurants (no register should block others). Essentially, we comply with the reality that partitions (devices offline) will happen, and we optimize for availability during those times.

Merge Transparency and Debugging

For compliance or analysis, you have an immutable record of user actions. No data is mysteriously lost by an automatic resolver. As one cross-cutting lesson: capture intent, don’t erase it. This also aligns with best practices for financial data enabling an audit trail of all actions (even conflicting ones) is often required.

No Custom Sync Code Needed

Because Ditto uses CRDTs to automatically resolve conflicts at a granular level, developers don’t need to write custom conflict resolution functions to merge document revisions in the sync layer. This stands in contrast to systems where you must supply conflict resolvers or risk losing data.

Proven at Scale

These strategies aren’t just theoretical. Major brands in the retail and restaurant industry have successfully employed similar patterns. It’s a battle-tested approach for large scale offline deployments. With Ditto, conflict resolution is not an add-on, but an intrinsic capability of the sync engine, so you can design your data with flexibility in mind from day one.

Conflict Resolution as a Competitive Advantage

By flipping the script on conflict resolution, we enable systems that are both more resilient and more transparent. The core idea is simple yet logical: don’t try to eliminate conflicts with strict ordering and document “locks”; instead, model your data to capture all outcomes and resolve the meaning in your application. This yields predictable and audit-friendly behavior even in extreme scenarios. What was once a perceived weakness becomes a strength: you gain observability, auditability, and uptime.

When designing offline-capable apps, ask yourself: “How do we currently observe and debug merge conflicts?” If the answer involves difficulty or guessing, consider embracing a conflict-free data approach. With techniques like duplicate recording of state and field-level CRDT merges, your application can shine a light on each step of the data’s journey. In the world of distributed systems, that transparency is gold. Developers retain control over business outcomes, and users get a seamless experience. By leveraging Ditto’s property-based sync and thoughtful data modeling, you turn potential chaos into an opportunity for a more robust and intelligent system.

Alongside this blog post, the Ditto team has built a demo application to showcase our conflict-free merge logic, enhanced observability, and offline resilience: Learn more here

Read more
Announcement
June 25, 2025
Ditto Forms Federal Advisory Board to Advance Strategic Growth in Public Sector
by
Adam Fish
Ditto is proud to announce the formation of its Federal Advisory Board, a strategic step in strengthening our commitment to the public sector, national security, and the growing role of cutting-edge technology in mission-critical environments.
May 29, 2025
Announcing A New Partnership with Internet Initiative Japan
by
Ryan Ratner
We’re excited to share some big news from across the Pacific: Ditto has entered into a marketing and implementation partnership with Internet Initiative Japan (IIJ), one of the most respected and well-established technology companies in Japan.