Skip to main content
When multiple clients write to the same data at the same time, you can get race conditions. Transactions give you atomic read-modify-write operations so that concurrent updates don’t step on each other.

Why transactions

Imagine two players both try to increment a score at the same time. Without transactions:
  1. Client A reads the score: 10
  2. Client B reads the score: 10
  3. Client A writes 11
  4. Client B writes 11
The score should be 12, but it’s 11. One increment was lost. Transactions prevent this.

Callback-style transactions

The most common pattern. You provide a function that receives the current value and returns the new value. Lark handles the retry logic. Here’s what happens under the hood:
  1. The SDK reads the current value at the path.
  2. Your function runs, receiving the current value and returning the new value.
  3. The SDK sends the new value to the server along with the expected current value.
  4. If another client changed the value between your read and write, the server rejects the transaction.
  5. Your function is called again with the updated value.
  6. This repeats until the write succeeds or the retry limit is reached (25 attempts).
Your transaction function may be called multiple times. It should be a pure function — no side effects, no network requests, no UI updates. Just compute a new value from the current value. Return undefined from your function to abort the transaction without writing anything.

Optimistic behavior

Transactions are optimistic by default. The client applies the result of your function to local state immediately, so your UI updates right away. If the server rejects the transaction and your function retries, the local state is updated again with the new result. This means your subscriptions fire immediately with the optimistic value, then potentially fire again if the server computes a different outcome. In practice, retries are rare and the optimistic value is almost always correct.

Multi-path transactions

Sometimes you need to update multiple paths atomically — either all the changes happen, or none of them do. Multi-path transactions support two syntaxes:
  • Object syntax — Pass an object where keys are paths and values are what to write. Simple and concise for straightforward atomic writes.
  • Array syntax — Pass an array of operations for more control, including the ability to add conditions.

Conditions (compare-and-swap)

In array-syntax transactions, you can add condition operations that check the current value before proceeding. If any condition fails, the entire transaction is rejected and no writes are applied. For primitive values (strings, numbers, booleans), the condition compares the value directly. For complex objects, Lark computes a SHA-256 hash of the current value and compares that. This lets you condition on “this object hasn’t changed” without specifying every field.
Use callback-style transactions for simple read-modify-write operations on a single path (like counters). Use multi-path transactions with conditions when you need to update several paths atomically or enforce preconditions.

Next steps