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.
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 and the Reflex vs Streamlit comparison.
Reflex vs Dash: faster component-heavy updates
The public component-scaling benchmark linked from the Reflex vs Dash comparison 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.
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, Reflex measurements, and Dash measurements.
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 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 in production mode.
Where work runs
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 and 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 runs predict_flower when the form is submitted. Editing an input does not run inference. In the linked XY charts, 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, 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 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 and streaming chat 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 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.