Understanding sync engines: How Figma, Linear, and Google Docs work
Learn how different realtime sync engines work, dive into the details of Yjs and CRDTs, and discover which collaboration infrastructure is right for your application.

Two users edit a rectangle in your design tool while offline. Alice moves it to
position [100, 200]. Bob changes its color to red. When they reconnect, what
should happen? This is easy to answer—show the rectangle at [100, 200] in
red. Both changes survive.
Now the hard questions:
- What if they both moved it to different positions?
- What if Alice deleted the rectangle while Bob moved it?
- What if they're both typing at the same position in a text field?
Your answers to these questions determine your entire sync architecture. Choose wrong, and you're looking at 3-6 months of refactoring when you realize users are losing data.
Every modern app is adding “realtime collaboration” to their feature list, and you've probably heard CRDT thrown around like it's a silver bullet. The reality is far more nuanced: Figma’s collaborative canvas works fundamentally differently than Google Docs’ text editing, which works differently than Linear's issue tracking. They’re all “realtime” and “collaborative”, but the underlying sync mechanisms solve different problems.
As someone who’s built realtime systems at scale—from social media platforms with offline-first sync to event-driven microservice architectures—I've seen how critical this architectural decision becomes. This guide synthesizes what production systems actually do and why.
Solving concurrent editing
Every realtime collaborative system must be able to handle multiple users editing the same data simultaneously, with network delays and offline periods. Changes can arrive in any order, and everyone needs to end up with the same result.
Let’s think back to Alice and Bob’s rectangle, and some conflicts they’ll face in a multiplayer app:
- Alice and Bob change different properties? Easy. Both changes survive.
- They edit the same property differently? Tricky. A conflict resolution strategy is needed.
- Alice deletes, Bob modifies? Tricky. An intention conflict needs to be resolved.
- Both typing in the same text position? Difficult. This is where most naive implementations break.
There are two solution families that handle these scenarios differently.
Operational Transformation (OT)
Operational Transformation (OT) transforms operations based on what others did concurrently. Requires a central server to order operations, and is used by Google Docs, Microsoft Office Online. Complex to implement correctly, but gives predictable results for text editing.
Conflict-free Replicated Data Types (CRDTs)
Conflict-free Replicated Data Types (CRDTs) operations are mathematically commutative—apply them in any order, always converge to the same state. Works peer-to-peer or client-server. True CRDTs underpin frameworks like Yjs and Automerge, powering many collaborative text editors and document tools.
CRDT-inspired architectures power Figma, Linear, and products built with Liveblocks like Vercel, Hashnode, and Resend.
What’s the difference?
Both enable “realtime collaboration”, but they’re designed for fundamentally different problems. Figma’s rectangle positioning needs different conflict resolution than Google Docs’ text editing. The key insight is that it’s not about OT vs CRDT—it’s about property-level vs character-level conflict resolution.
Conflict resolution methods
Property-level conflict resolution treats each attribute of an object as an independent unit. When two users edit different properties of the same object, both changes succeed. When they edit the same property, Last Write Wins (LWW) based on a logical clock.
Character-level conflict resolution treats text as a sequence of characters with positional metadata. When two users edit different parts of the same text, both edits are preserved in the final result through sophisticated merging algorithms.

Property-level uses last-write-wins per property, where character-level merges based on position.
Concrete Examples
Two users are editing a task item while offline:
This is property-level conflict resolution, used by Figma and Liveblocks Storage. Each property is independent, and changes to different properties never conflict.
Now consider the same scenario with text editing:
This is character-level conflict resolution, used by Google Docs and Yjs. The CRDT tracks the position and context of each character insertion, allowing both edits to coexist in the final document. This difference determines your entire sync architecture.
The theory is interesting, but what do real companies with millions of users actually do? Let’s examine three battle-tested systems.
Which systems are used in production?
Figma: Property-level Last Write Wins
Figma’s multiplayer system is one of the most well-documented collaborative architectures. How it works:
Figma’s servers track the latest value for each property on each object, as described on Figma’s engineering blog. When two clients change different properties (one moves the rectangle, another changes its color), both changes succeed. When two clients change the same property, the last change received by the server wins.
Figma's multiplayer servers keep track of the latest value that any client has sent for a given property on a given object. This means that two clients changing unrelated properties on the same object won't conflict.Evan WallaceCo-founder, Figma
This is explicitly not a true CRDT because it relies on a central server for ordering, but it’s inspired by CRDT concepts—specifically the Last-Writer-Wins Register pattern. Liveblocks Storage uses this same proven architecture with centralized CRDT-like structures. I’ve implemented similar patterns in production social platforms—the simplicity is what makes it reliable at scale. Figma chose this approach because:
| Reason | Impact |
|---|---|
Design tools work with discrete objects | Each shape, layer, or component is an independent entity |
| Properties update independently | Color changes don’t conflict with position changes |
Users rarely edit same property simultaneously | Natural work patterns reduce conflicts |
| Last-write-wins is intuitive | When conflicts occur, newest wins matches user expectations |
Significantly simpler than full CRDTs | Faster performance, easier to debug |
Figma’s approach doesn’t work for text editing. If the text “B” becomes “AB” on one client and “BC” on another, the result is either “AB” or “BC”—never “ABC”. This is fine for design tools but unacceptable for collaborative text editors.
Linear: Mostly Last Write Wins with selective CRDTs
Linear’s sync engine is designed for a more varied data model than Figma (as documented in their blog and reverse-engineered here) and uses a similar property-level approach for most data:
Linear tracks changes as discrete events, each modifying a single attribute. The server assigns each change a monotonically increasing sync ID, and conflicts resolve to the highest ID (effectively last-write-wins, but server-ordered).
Linear Design Choice | Rationale |
|---|---|
| Event-based architecture | Every change is a separate event with metadata |
| Property-level granularity | Status changes don't conflict with title edits |
| Server-ordered sync IDs | Reliable ordering through centralized transaction processing |
| Last-Write-Wins default | Conflicts are rare; simple resolution works |
| CRDTs only for rich text | Added later for issue descriptions only |
According to their engineering team's discussions, conflicts are actually quite rare in their domain—most of the time users are working on different issues or different properties of the same issue. They only recently added CRDTs for one specific use case; rich-text editing in issue descriptions.
Google Docs & Yjs: Character-level OT & CRDTs
Both Figma and Linear use property-level approaches because they work with discrete objects where conflicts are rare. Text editing is fundamentally different—users constantly insert characters at nearby or identical positions. This is where character-level resolution becomes essential.
Google Docs and Yjs both handle character-level text editing, but use different approaches. Google Docs uses Operational Transformation (server-based), while Yjs uses CRDTs (peer-to-peer). Both recognize that text editing has fundamentally different conflict patterns than object properties.
How Yjs is structured
Here’s how Yjs structures text for CRDT-based collaboration:
Every character gets a unique identifier tracking its position and creation context. When two users type at the same spot simultaneously, Yjs uses a deterministic algorithm to decide which text comes first based on these identifiers:
This is fundamentally different from property-level sync. Yjs operates on ordered sequences—treating text as individual characters with positional relationships. You can’t just “swap in Yjs” for a Figma-like app because it’s solving a different problem: preserving the order and intent of concurrent insertions in sequences, not resolving conflicts between discrete object properties.
You’ve seen what Figma, Linear, and Google Docs do, now let's determine which approach fits your application.
Which approach do you need?
When to use property-level resolution (Liveblocks Storage)
Property-level resolution is fully supported by Liveblocks Storage.
Use Case Category | Examples | Why property-level works |
|---|---|---|
| Visual/spatial editors | Figma, Miro, Whimsical, Lucidchart | Objects have discrete properties (position, size, color) |
| Diagramming tools | Draw.io, flowchart builders | Nodes and edges are independent objects |
| Structured data apps | Kanban boards, form builders, spreadsheet cells | Each item has distinct properties that rarely conflict |
| Configuration tools | Dashboard builders, workflow designers | Changes typically affect different objects/properties |
Conflict characteristics that favor property-level:
- Users typically work on different objects or different properties.
- When same-property conflicts occur, last write wins is acceptable.
- You need to track “who changed what” at the property level.
- Objects have clear identity and structured properties.
Code example
LiveObject is a CRDT-like
data structure that allows these changes to be merged automatically.
It’s also possible to use LiveMap and LiveList to manage complex data structures, nesting them inside each other.
When to Use Character-Level Resolution (Liveblocks Yjs)
Character-level resolution is fully supported by Liveblocks Yjs and our text editor integrations.
Use Case Category | Examples | Why Character-level Is required |
|---|---|---|
| Text/rich-text editors | Google Docs, Notion, Medium | Multiple users typing in same paragraph |
| Code editors | VSCode Live Share, Replit | Character-level precision for code |
| Markdown editors | HackMD, CodiMD | Text content is primary data |
| Messaging/comments | Slack-like apps with rich text | Insertion order and position matter |
Conflict characteristics that require character-level:
- Multiple users editing the same text simultaneously.
- Insertion position and order are critical.
- Character-level granularity is required.
- Delete operations need to be preserved.
- Undo/redo must work correctly in collaborative context.
Code example
Liveblocks Yjs is a CRDT-like data structure that allows these changes to be merged automatically.
The Decision Tree
Use this tree to determine which approach to use for your application.

What actually matters in production
After studying production systems:
Technique | Why It Matters | When to Use |
|---|---|---|
| Property-level LWW | Matches user mental models for object editing | 90% of design tools, structured data apps |
| Character-level CRDTs | Required for concurrent text editing | Any collaborative text/code editor |
| Logical clocks | System timestamps are unreliable in distributed systems | Distributed systems without central ordering authority |
| Optimistic updates | Users expect instant feedback | Every realtime collaborative app |
| Presence/awareness | Seeing others' cursors is table stakes | Apps with multiple simultaneous editors |
What doesn't matter as much:
- OT vs CRDT debate: Yjs and CRDTs won. Use CRDTs.
- P2P vs client-server: Client-server is simpler and works fine for most use cases.
- Custom CRDT implementations: The research is solved. Use existing solutions.
At this point you might be thinking: “This seems straightforward—can’t I just build it myself?” Here’s what that actually entails.
Why not roll your own?
The decision framework seems straightforward, but you might be thinking: “Can’t I just build this myself?”. It’s harder than it looks, which is why platforms exist to solve this. When I talk to clients who built their own sync engine, they consistently underestimated the following components.
Logical clocks that actually work
You can’t just use Date.now() for timestamps. System clocks drift, users
change their time zones, and servers can have clock skew. One approach is Hybrid
Logical Clocks.
Note that systems with centralized servers (like Linear and Figma) can sidestep this complexity with server-assigned ordering, but truly distributed systems need HLCs or similar mechanisms. The original HLC paper details why this matters for distributed systems.
Efficient storage and indexing
You need a database schema that handles millions of events efficiently. Here’s the difference:
The production schema tracks both client-side causality (client_id +
client_sequence) and server-side ordering (server_timestamp). When conflicts
occur, server timestamp wins. The UNIQUE constraint prevents duplicate
operations from the same client.
This approach, used by Linear and Figma, is simpler than full vector clocks but reliable because the server provides total ordering.
What you’re actually building
Before you know it, an array of tasks will be on your hands.
Component | Complexity | Why it’s hard |
|---|---|---|
| Logical clocks | High | HLCs require careful implementation; bugs cause silent data corruption |
| Database Schema | Medium | Must handle millions of events, efficient queries, garbage collection |
| Offline Sync | Very High | Queuing, reconnection, merge conflicts, UI updates during merge |
| Presence System | Medium | Separate broadcast system for cursors, selections, typing indicators |
| Permission Control | High | Row-level security, encryption, audit logs, graceful access revocation |
| WebSocket Infrastructure | Very High | Connection pooling, load balancing, heartbeats, mobile sleep/wake handling |
6-12 months of engineering time from experienced distributed systems engineers, plus ongoing maintenance, monitoring, and debugging infrastructure.
Practical Implementation
The theory is clear. Now let’s build it. You have two options:
Option 1: Build it yourself (6-12 months, 2-3 engineers)
- Implement logical clocks, conflict resolution, WebSocket infrastructure.
- See Why not roll your own? for the full task list.
Option 2: Use Liveblocks (hours to days)
- Handles sync infrastructure, storage, and conflict resolution.
- Two products matching the patterns above.
Implementing these patterns with Liveblocks
This complexity is exactly why Liveblocks exists—to handle the difficult sync infrastructure so you focus on your application. Liveblocks is the only hosted solution that offers both sync types, providing:
- Liveblocks Storage for property-level sync (the Figma/Linear pattern)
- Liveblocks Yjs for character-level sync (the Google Docs pattern)
Let’s see how to implement each:
Liveblocks Storage: Property-level sync
Architecture pattern:
Key capabilities:
- LiveObject, LiveMap, LiveList CRDT-like data structures.
- Automatic conflict resolution per property.
- Optimistic updates with automatic sync.
- Connection handling with offline detection.
- Built-in presence (cursors, selections).
- Packages for React, Zustand, Redux.
Liveblocks Yjs: Character-level sync
Architecture pattern:
Key capabilities:
- Full Yjs CRDT implementation.
- Managed WebSocket infrastructure.
- Persistent storage for Yjs documents.
- Easy integrations for popular text editors: Lexical, Tiptap, BlockNote.
- REST API and Node.js packages for server-side modifications.
Using both together
Many applications need both:
Conclusion
The collaborative sync landscape has matured significantly. The fundamental patterns, property-level LWW for objects, character-level CRDTs for text, are well understood and battle-tested in production (Figma, Linear, Yjs).
These are different problems requiring different solutions. Figma doesn't use Yjs because property-level sync is the right model for design tools. Google Docs doesn't use property-level sync because character-level CRDTs are required for text editing.
Choose based on your primary data model:
If you're building... | Use... |
|---|---|
Objects with properties (design tools, kanban boards) | Liveblocks Storage |
Text and ordered sequences (editors, documents) | Liveblocks Yjs |
| Both (Notion-style apps) | Use both thoughtfully |
The techniques presented here represent battle-tested approaches from production systems serving millions of users. While there are other sync engines and approaches out there, the decision framework remains the same: match your data model to the right conflict resolution strategy.
Now go build something collaborative. Reach out if you get stuck.
Further Reading
Ready to get started?
Join thousands of companies using Liveblocks ready‑made collaborative features to drive growth in their products.


