Evaluation and Purity
Dependency Tracking
Section titled “Dependency Tracking”A binding is evaluated lazily: its expression runs when the property’s value is queried, not when the binding is set.
While a binding’s expression is evaluated, every property it reads is registered as a dependency of that binding. When any of those properties changes, the binding is marked dirty. The next query of the property re-evaluates the expression and rebuilds its dependency set from the properties read during that evaluation. A binding whose expression reads no properties is never marked dirty and is evaluated at most once.
The dependency set is rebuilt on each evaluation, so it always reflects the properties read on the most recent run. A property read on a path that a later evaluation does not take is not a dependency of that evaluation.
Overwritten Bindings
Section titled “Overwritten Bindings”A property holds at most one binding. Setting a new binding on a property replaces any previous one; the last binding wins.
A binding is active only as long as the property is assigned with a binding expression (x: other.value) or a two-way binding (<=>).
An imperative assignment (foo.bar = 42;) removes the binding:
from that point on the property no longer reacts to the values it was bound to.
The property can still be updated through further assignments, but its automatic reactivity is lost.
An assignment intercepted by a two-way binding is the exception: it writes through the link and leaves the binding in place.
This also applies to most built-in in-out properties, such as the text property of a TextInput:
user interaction writes the property like an imperative assignment and removes an existing binding to it.
To keep reactivity in such cases, use a two-way binding, or track updates with a change callback.
Purity
Section titled “Purity”Evaluating a property binding must not change any observable state other than the property itself. An expression with this quality is pure; otherwise it has side effects. Because bindings are evaluated lazily, the order of side effects and whether they happen at all are not defined, so bindings must be pure.
The compiler enforces that code in a pure context is free of side effects. The pure contexts are:
- Property binding expressions.
- Model expressions of a
forelement. - The body of a function or callback declared
pure.
In a pure context, the following are compile errors:
- An assignment or self-assignment (
=,+=,-=,*=,/=) to a property. - A call to a callback that is not
pure. - A call to a function that is not pure.
- A call to a built-in function that has side effects.
The pure keyword annotates callbacks and functions
so they may be called from a pure context.
A pure callback’s handler and a pure function’s body are themselves pure contexts, and are checked accordingly.
export component Example { pure callback foo() -> int; public pure function bar(x: int) -> int { return x + foo(); }}The purity of a private function is inferred: the compiler treats it as pure exactly when its body contains no side effects.
A private function may still be declared pure to have the compiler reject a body that has side effects.
A function that implements a pure function from an interface must itself be pure; declaring it impure is a compile error.
The reverse is allowed: a pure implementation may satisfy an impure declaration.
Purity must match across a callback alias or a callback two-way binding:
both sides must be pure or both impure, otherwise it is a compile error.
© 2026 SixtyFPS GmbH