Real cross-platform mobile frameworks (classic React Native's bridge, Cordova/Ionic's WebView bridge) keep the JS/Dart logic thread separate from the native UI thread. Every UI update that has to reach the native side is serialized, sent across the bridge, and deserialized on the other end — and that crossing has a real fixed cost no matter how big the payload is (JSON encode/decode, thread-hop, message-queue dispatch).
num_calls = ceil(total_updates / batch_size)
call_time = fixed_overhead + updates_in_call × per_update_cost
total_time = Σ over all calls of call_time
= num_calls × fixed_overhead + total_updates × per_update_cost
Batching means grouping multiple UI updates into a single bridge call instead of firing one call per update. The fixed overhead is paid once per call, not once per update, so a larger batch size amortizes that overhead across more work — fewer, larger crossings instead of many small ones. That's why total_time falls steeply as batch size grows from 1, then flattens out: once fixed_overhead is a small fraction of total_time, the remaining cost is dominated by total_updates × per_update_cost, which does not change with batch size — diminishing returns.
- Total UI updates — how many individual update messages the JS thread needs to deliver this "frame".
- Batch size — how many updates are packed into one bridge call before crossing.
- Fixed overhead — serialization/deserialization + thread-hop cost paid once per call, regardless of batch size.
- Per-update cost — marginal serialization cost per update inside the payload.
Real-world relevance: this is exactly why React Native batches UI updates at the end of a JS event-loop tick instead of bridging every state change individually, and why Flutter/JSI architectures that skip the bridge entirely avoid this overhead altogether.