# Performance best practices

This page sums up the principles behind fast dcupl applications. The options, thresholds and code for each of them are in the SDK guide [Performance optimization](/sdk/advanced/performance).

## Performance principles

### Initialize once, update incrementally

`init()` processes all models and data. Call it once at startup. Afterwards, stage changes with `data.update()`, `data.upsert()` or `data.remove()` and apply them with `await dcupl.update()`; never call `init()` again to apply a change. See [Partial Updates](/sdk/advanced/performance#partial-updates).

### Prefer partial updates, and batch them

A partial update reprocesses only the changed items and the attributes that depend on them. `data.set()` and `data.reset()` always trigger a full rebuild, which re-runs the processing of `init()` over all models, and dcupl also rebuilds fully when more than half of the models and more than half of their items changed, when a model definition changed after `init()` (`models.set()`, `models.update()` or `models.remove()`), or when the data a query reference depends on changed. See [When dcupl Falls Back to a Full Update](/sdk/advanced/performance#when-dcupl-falls-back-to-a-full-update). Each `dcupl.update()` call has a fixed cost, so stage a batch of changes and apply it with one call.

### Filter on indexed properties

References are indexed, and `index: true` adds an index to a property. `eq` and `in` conditions on them are index lookups; other operators evaluate the items. In an `'and'` group, conditions run in order on the items that are left, so put the most selective scanning conditions first. Index-backed `eq` and `in` conditions are cheap in any position. See [Query Optimization](/sdk/advanced/performance#query-optimization).

### Flatten deep queries

Each level of a deep query such as `customer.country` resolves one more reference for every evaluated item. Use a [derived property](/sdk/advanced/derived-properties) for paths you filter on often, keep paths to 2–3 levels, and see [Data modeling](/learn/best-practices/data-modeling) for how to structure references.

### Read pages, not everything

Pass `start` and `count` to a read: `items({ start: 50, count: 50 })`. Paging stored with `applyOptions({ start, count })` works too; `count()` then returns the page length, and `fn.metadata().currentSize` the number of all matches. See [Use Pagination](/sdk/advanced/performance#use-pagination).

### Reuse and destroy lists

Create a list once and change its query instead of creating a list per search. Every `dcupl.lists.create()` registers a list that stays alive until you call `destroy()`. See [List Management](/sdk/advanced/performance#list-management).

### Load only what you need

Select resources with tags, leave out properties the app never reads, and render long result lists virtualized. See [Memory Optimization](/sdk/advanced/performance#memory-optimization) and [Large Dataset Handling](/sdk/advanced/performance#large-dataset-handling).

### Measure with your own data

Timings depend on your models, your data and the device. Time `init()`, `dcupl.update()` and your reads with `console.time()`, or read the `duration` of the `dcupl_initialized` and `dcupl_updated_manually` events. See [Profiling and Debugging](/sdk/advanced/performance#profiling-and-debugging).

## What holds in general

- **One `dcupl.update()` per batch** is far faster than one `dcupl.update()` per change.
- **Partial updates** (`data.update()`, `upsert()`, `remove()`) are faster than replacing a model with `data.set()`.
- **Repeated reads of an unchanged query** are served from the list's cache until the next update that affects the list.

## Performance checklist

- [ ] `init()` runs once, at startup
- [ ] Changes are staged and applied with one `await dcupl.update()` per batch
- [ ] Update payloads hold only the changed attributes
- [ ] Exact-match filters use references or `index: true` properties
- [ ] Frequent deep query paths are derived properties
- [ ] Large result sets are read page by page
- [ ] Lists are reused, and destroyed when no longer needed
- [ ] Only the data and properties the app needs are loaded

## Next steps

- [Performance optimization](/sdk/advanced/performance) — The SDK guide with options, thresholds and examples
- [Data modeling](/learn/best-practices/data-modeling) — Structure models effectively
- [Error handling](/learn/best-practices/error-handling) — Handle errors gracefully
- [Caching](/sdk/advanced/caching) — Caching strategies
