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.

Transaction use cases

Imagine two players both try to increment a counter at the same time. Without transactions:
  1. Client A reads the counter: 10
  2. Client B reads the counter: 10
  3. Client A writes 11
  4. Client B writes 11
The counter 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 value of the path that your client saw when it made the change.
  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, where 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, to be efficient and avoid sending the entire object’s data over the wire, 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. This is handled for you and you don’t need to worry about how calculating the hash yourself, but it’s useful to know how to works in the background.
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

Lark SDK transactions

Full API reference and code examples for transactions in the Lark SDK.

REST API conditional requests

Compare-and-swap over HTTP using ETags.