Object References and Copies

Objects on the canvas are passed by reference. Updating one can change what another block already read. This page shows when that happens and how to take a copy.

An object pin does not carry a snapshot of the object. It carries the object itself. Two blocks connected to the same object out-pin are looking at the same thing in memory, so a change made through one of them is visible to the other, including to blocks that already ran.

That is what makes flows cheap: a document of a few hundred fields is passed around without being copied on every hop. It is also the source of a specific class of bug, where a value changes underneath a block that looked at it earlier.

Which blocks change the original

Block What it does with the input
object_update_fields Applies the mutations and field pins to the object you connected, then outputs that same object. The original changes.
object_new_fields Builds a fresh object from its in-pins. Nothing you connected is modified.
object_shallow_copy Builds a new object with the same top-level keys. Nested objects and arrays are still shared with the original.
object_deep_copy Builds a new object with no shared references at any depth. The safest, and the most expensive.
The rule of thumb Reading is free. The moment a flow writes to an object it did not create, decide first whether the writer should own a copy. If any other branch of the flow still needs the untouched values, copy before you write.

Shallow copy versus deep copy

A shallow copy protects the top level only. Take a customer object with a nested address: after a shallow copy, setting status on the copy leaves the original alone, but setting address.city changes the original as well, because both objects point at the same nested address.

A deep copy rebuilds every level, so nothing is shared. It costs time and memory proportional to the size of the object, which is why the block's own description warns against widespread use. Reach for it when you genuinely need an independent snapshot, for example when you keep a "before" version of a document to compare against or to log.

Shared nested value
original = {
  "name": "joe doe",
  "address": { "city": "Amsterdam" }
}

copy = shallow_copy(original)
copy.name = "sami b"          // original.name is still "joe doe"
copy.address.city = "Utrecht" // original.address.city is now "Utrecht" as well

copy = deep_copy(original)
copy.address.city = "Utrecht" // original.address.city stays "Amsterdam"

Where this bites in practice

  • Keeping a before and after. Reading a document, updating it in place, and then trying to log "what it was" gives you the new values twice. Deep-copy the document before the update if you want the old state.
  • Iterations. Updating the object of the current item inside a loop mutates the entry in the array you are iterating over. That is fine when it is what you meant, and confusing when it is not.
  • Reusing a template object. Building one base object and updating it per iteration leaves every reference pointing at the final state. Build a new object per iteration, or copy the template inside the loop.
  • Passing to a function. A custom function that updates the object it received changes the caller's object too. Copy at the boundary if the function is meant to be a pure transformation.

Storage documents follow the same rule

A document read from storage is an ordinary object on the canvas, so everything above applies to it. Writing to it does not write to storage: persisting still goes through mutations and function_update_document, described in Storages. What in-place updates do change is what the rest of your flow sees.

Next steps

Locking and Concurrency The other half of the story: what happens when two flows touch the same data at the same time. Block Execution The order blocks run in, and when a value is resolved. Common pitfals The other mistakes that cost an afternoon.

Frequently asked

Does updating an object in RUAL change the original?

It depends on the block. object_update_fields applies its changes to the object you connected and outputs that same object, so the original changes. object_new_fields builds a new object and leaves the input untouched.

What is the difference between a shallow copy and a deep copy in RUAL?

A shallow copy creates a new object with the same top-level keys, but nested objects and arrays are still shared with the original. A deep copy rebuilds every level, so nothing is shared. Deep copies cost time and memory proportional to the object size.

How do I keep the old version of a document before updating it in RUAL?

Take a deep copy before you apply the update. Updating in place changes the object every other block is holding, so without a copy the before and after values are the same.