> For AI agents: the complete documentation index is at [llms.txt](https://reflex.dev/docs/llms.txt). For a Markdown version, remove the trailing slash from the page URL and append `.md`. The docs home is available at [index.md](http://localhost:3000/docs/index.md).

---
title: Reflex Performance — Streamlit and Dash Benchmarks
meta_description: Compare Reflex performance with Streamlit and Dash using published dashboard and component benchmarks. Learn how rendering and state updates affect latency.
---

# Performance and execution

Reflex combines a customizable React interface with explicit Python state and event handlers. Published comparisons show concrete performance gains over Streamlit and Dash: faster initial data visibility in an NBA dashboard and faster bulk updates in a component-heavy form. The measurements below explain where those gains appeared and how to apply the same rendering and state-management techniques in your app.

## Reflex vs Streamlit: faster time to visible data

In Reflex's internal NBA dashboard benchmark, the player table appeared in **2.13 seconds with Reflex versus 15.56 seconds with Streamlit** on simulated slow 4G: about **7.3× less waiting for the initial data**.

| Network profile | Reflex | Streamlit | Cold runs |
| --- | --- | --- | --- |
| Local | 436 ms | 681 ms | 7 |
| Cable | 484 ms | 1,727 ms | 5 |
| Simulated slow 4G | 2,132 ms | 15,556 ms | 3 |

These are median production-mode measurements from June 3, 2026, on one Linux workstation using Chromium. The metric is the later of first contentful paint and table rows entering the DOM. Slow 4G simulated 1.6/0.75 Mbps, 150 ms RTT, and 4× CPU slowdown.

The Reflex app prerendered its first table page. Its development-version branch also included tree-shaking and font-preload improvements; compression differed between implementations. These results measure initial visibility, not full interactivity or filtering latency. Reflex used more idle server memory: 327 MB versus 175 MB PSS.

Read the [benchmark report and reproduction limits](https://reflex.dev/compare/streamlit-benchmark/) and the [Reflex vs Streamlit comparison](https://reflex.dev/compare/streamlit/).

## Reflex vs Dash: faster component-heavy updates

The public component-scaling benchmark linked from the [Reflex vs Dash comparison](https://reflex.dev/compare/dash/) reports **1.2 seconds in Reflex versus 29 seconds in Dash** to write all checkbox values in a form with roughly 5,200 mounted components: about **24× less time for that bulk update**.

| Workload | Reflex | Dash |
| --- | --- | --- |
| Initial load, ~5,200 configured components | ~305 ms | ~22 s |
| Initial load, ~17,200 configured components | ~215 ms | ~364 s |
| Write all checkboxes, ~5,200 mounted components | ~1.2 s | ~29 s |

These are third-party **development-mode** results. The initial-load implementations use different mounting strategies: Reflex leaves collapsed checkbox groups unmounted with `rx.cond`, while Dash mounts them upfront. Expanding all ~17,200 components in the Reflex example took ~5.3 seconds. The bulk-update row changes many checkbox values; it is not a single-field latency measurement.

The practical lesson is to mount expensive panels when needed and limit broad state updates. Both frameworks incur more browser work as mounted content grows. See the [benchmark source and setup](https://github.com/olincb/dash-vs-reflex), [Reflex measurements](https://github.com/olincb/dash-vs-reflex/blob/main/reflex_app/README.md), and [Dash measurements](https://github.com/olincb/dash-vs-reflex/blob/main/dash_app/README.md).

## Apply the performance advantages in your app

Reflex gives you control over when UI components mount, which Python handler runs, and which state changes reach the browser. Combine those controls with component-level CSS, responsive layouts, and [React component wrappers](/docs/wrapping-react/overview/) to build a custom application around your dashboard.

The published results above were reported by their authors and have not been independently reproduced for this guide. They demonstrate gains for those implementations, rather than a universal speed ranking. Use the execution model below to identify your bottleneck, then [measure an equivalent workload](#measure-an-equivalent-workload) in production mode.

## Where work runs

| Work | Where it runs | What to measure |
| --- | --- | --- |
| Python component construction | During app compilation | Build time and generated frontend size |
| React rendering and component-local interaction | Browser | Load time, rendering, and responsiveness |
| Var expressions compiled to JavaScript | Browser | Expression and render cost |
| Ordinary Python state event handler | Backend | Queueing, handler time, and state-update size |
| Database query or provider call | Backend and external service | Network and service duration |
| Background event | Backend task | Work duration and time spent holding the state lock |

Reflex compiles the UI to React. Ordinary Python state handlers execute on the backend, with events and state updates carried over the connection to the browser. It is inaccurate to say that no logic runs in the browser: component-local interactions, compiled expressions, and explicitly added JavaScript can run there. See [how Reflex works](/docs/advanced-onboarding/how-reflex-works/) and [custom code and hooks](/docs/wrapping-react/custom-code-and-hooks/).

## Avoid repeating unrelated work

An ordinary Reflex event invokes its handler; it does not re-execute the entire Python page definition. The state system tracks changed values and sends state updates to the frontend. Keep a database load in the event that needs it, and let a separate UI event update only the selection or display state it owns.

For example, the [model demo](/docs/guides/model-and-media-interfaces/) runs `predict_flower` when the form is submitted. Editing an input does not run inference. In the [linked XY charts](/docs/getting-started/linked-charts-tutorial/), drawing a selection is local to the chart; the completed selection triggers the Python cross-filter. Those boundaries give you control over when backend work runs.

This architecture can avoid repeated work, but it does not guarantee that every Reflex app outperforms every alternative. An event handler can still call an expensive function, and a computed value can still perform an expensive calculation. Measure those costs with the same inputs and cache policy when comparing implementations.

## Follow one interaction

In the [linked charts example](/docs/getting-started/linked-charts-tutorial/), XY handles drawing the selection locally. A completed selection sends a bounded selection envelope to a Python handler. The handler records selected IDs, computed values produce the linked chart data and rows, and the browser renders the changed views.

That path includes a network round trip and backend work. A chart's local hover effect may follow a different path. Do not use one interaction's timing as a claim about every interaction or every framework.

## Keep data and updates bounded

Load only records the current view needs. Use database aggregation for chart summaries, server-side pagination for large tables, and bounded history for chat and streaming feeds. Backend-only data avoids transmitting it as ordinary frontend state, but backend memory and serialization requirements still matter.

Use [computed vars](/docs/vars/computed-vars/) for derived data, and understand their dependencies before putting expensive queries in them. A UI re-evaluation should not accidentally become an unbounded database workload. Profile the actual handler and query instead of assuming that a slow response means React rendering is slow.

## Use background events deliberately

An async handler can await I/O, but a background event is the mechanism for work that may proceed alongside other state events. In a background event, enter `async with self` to obtain fresh state and make changes. Keep those sections short: copy the inputs you need, release the lock, await the service, and reacquire the lock to apply the result.

The [background events guide](/docs/events/background-events/) and [streaming chat tutorial](/docs/getting-started/chatapp-tutorial/) demonstrate this pattern. A result can arrive after the user changes a filter or cancels a request, so check whether it still belongs to the current operation before applying it.

Async code does not make CPU-heavy Python work non-blocking. Use an appropriate worker or process strategy for expensive computation. Background events also do not provide durable queue semantics such as persisted jobs, restart recovery, or guaranteed retries. Put those requirements in a worker system and store job status explicitly.

## Measure an equivalent workload

Before comparing two implementations, record:

- Framework and dependency versions, production build configuration, hardware, and process count.
- The same dataset, displayed rows, chart traces, mounted controls, and user action.
- Client location, backend location, network latency, and relevant service dependencies.
- Cold and warm runs separately, concurrent sessions, and the number of completed measurements.
- Median and p95 event-to-visible-result latency, payload size, backend memory, and error rate.

Measure first page load separately from a filter change, a streamed answer's first token, total generation time, compilation, and deployment preparation. A comparison of different mounted UI sizes or different network topologies cannot establish a general “fastest framework.”

## Production execution

Use the [self-hosting guide](/docs/hosting/self-hosting/) for the current production command and server configuration. Test long-lived connections, reconnects, state persistence, and concurrent sessions in your intended deployment topology. A local single-user benchmark is not evidence of capacity under production traffic.
