Building Performant UIs for Large Datasets (Qt)
Oct 11, 2025Jul 07, 2026
If you’ve ever tried to show tens of thousands of rows in a Qt table, you’ve seen the freezes: jerky scrolling, stalled resizes, filters that lag behind your typing.
The problem isn’t the amount of data — it’s how much unnecessary work the UI does per interaction.
Here’s a practical, impact-ordered list of what actually moves the needle.
1) Freeze Before Chaos
During window or column resize (or any large-scale update), every intermediate repaint and layout recalculation costs dearly.
Freeze updates, do the work, then repaint once.
# Around bulk operations or during resize (after a short debounce)
self.setUpdatesEnabled(False)
self.setSortingEnabled(False)
# ... modify model or resize columns ...
self.setSortingEnabled(True)
self.setUpdatesEnabled(True) # one clean repaint
This alone can eliminate the worst stutters.
2) Batch Everything
Per-row signals and inserts cause layout thrash. Emit updates in chunks — the table view and model handle grouped updates far more efficiently.
BATCH = 100
bucket = []
for row in rows:
bucket.append(row)
if len(bucket) == BATCH:
model.addRows(bucket) # single insert & signal emission
bucket.clear()
if bucket:
model.addRows(bucket)
3) Cache Hot Paths
data() can be called thousands of times per second. Cache precomputed or formatted values to avoid repeating expensive operations.
key = (row, col)
val = cache.get(key)
if val is None:
val = cache[key] = compute(row, col)
return val
A small in-memory cache can make scrolling several times smoother.
4) Defer Heavy Work
The UI thread should only draw, not process.
Load fast, process later — move CPU-heavy logic into a worker or deferred queue.
The table becomes interactive immediately, and the rest of the work happens quietly in the background.
5) Pagination & Streaming
Never block the UI waiting for all rows to load.
Use canFetchMore() / fetchMore() to page data (e.g. 500–1000 rows at a time), or stream chunks progressively into the model.
PAGE = 1000
def canFetchMore(self, _p): return self.loaded dataChanged signal per frame (~30–60 FPS).
It feels instant, but avoids hundreds of redundant redraws.
## 8) Scale-Aware View Policy
Adjust behavior based on row count:
- Fixed row height and ScrollPerPixel for smoother motion.
- Avoid ResizeToContents on large tables — use Stretch or Fixed instead.
- Auto-fit columns only for small datasets or after the resize settles.
header = table.verticalHeader()
header.setSectionResizeMode(QHeaderView.Fixed)
header.setDefaultSectionSize(22)
table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
### Summary
Performance is **strategic laziness**:
- Do less (freeze & coalesce).
- Do it later (defer & lazy-load).
- Do it in batches (batch, stream, paginate).
If you can only do one thing today, start by freezing during resizes and bulk operations.
Then layer in batching, caching, and pagination — your Qt tables will finally _feel_ as fast as they look.

