In Plotly Dash, how does the callback model differ from Streamlit's script rerun?
answer
- one re-runs everything, one re-runs a subgraph
- where does per-user state live?
- one server remembers you, the other does not
- think sticky sessions versus plain workers
- declared dependencies versus top-to-bottom code
basics
~20 sStreamlit reruns the whole script top to bottom on every interaction, with per-session state kept by the server. Dash runs only the callbacks whose declared inputs changed, on a stateless server, with state living in the browser.
solid answer
~50 sStreamlit's model is a **rerun**: any widget interaction re-executes your script from line one, and the widget functions return the current values, so the code reads like a linear notebook. State that must survive lives in `st.session_state` or behind `st.cache_data`, and the server holds a session per connected user. Dash's model is a **dependency graph**: you declare `Output`/`Input`/`State` on each callback, the browser knows which callbacks a change affects, and only those run. Nothing outside a callback re-executes, and the server keeps no per-user state — it is a Flask app that can sit behind several gunicorn workers, so browser-side `dcc.Store` or an external cache holds anything shared between callbacks. The trade is real: Streamlit gets you to a working app faster and reads more naturally; Dash gives finer control over what recomputes, arbitrary layout, and a scaling story that does not depend on session affinity.
go deeper
Know the one-sentence contrast — one framework re-runs the script, the other runs only the callbacks whose inputs changed — and that both are Python-only ways to build a web app.
Explain what follows from each model: caching and session_state on one side, a declared dependency graph and browser-held state on the other, and why the servers differ in statefulness.
Bring the operational angle: sticky sessions versus plain workers, where a per-keystroke warehouse query hides in each model, and how you would decide for a team already running Flask infrastructure.
Own the build-versus-buy line. Argue when an internal data app is the right answer at all, what owning it costs over three years in upgrades and on-call, and where the governed BI platform is cheaper.
## Two answers to the same problem Both frameworks let a Python author ship an interactive web app without writing JavaScript. They solve the "what happens when the user clicks something" problem in opposite ways. ## Streamlit: rerun the script A Streamlit script executes top to bottom. `st.selectbox(...)` both renders a widget and returns its current value, so downstream code is ordinary Python using that value. When the user changes the widget, Streamlit re-runs **the entire script** in that user's session, and the widget call now returns the new value. There is no event handler, no wiring, no ids. The consequences follow directly: - **Everything recomputes** unless you stop it. That is why caching is central: `st.cache_data` for serialisable results such as a DataFrame, `st.cache_resource` for shared objects such as a database connection. - **State must be parked deliberately.** Local variables die with each run, so anything that must persist across reruns lives in `st.session_state`, a per-session dictionary. - **The server is stateful and session-bound.** Each browser session holds server-side state over a websocket, which means scaling out needs sticky routing and per-session memory. ## Dash: run the affected callbacks A Dash app declares a layout of id'd components and a set of callbacks. Each callback names the component properties it reads (`Input`, `State`) and writes (`Output`). Dash ships that dependency graph to the browser. When a property changes, the front end computes exactly which callbacks are affected, POSTs their inputs to the server, and patches the returned outputs into the DOM. Chains resolve automatically: if callback A's output is callback B's input, B runs after A. The consequences here: - **Only the touched subgraph recomputes.** A change to one filter does not re-run the four unrelated panels, and you can see the whole graph in the callback-graph dev tool. - **The server is stateless.** A callback is a pure request handler. There is no per-user memory between requests, so global variables are not per-user state — with multiple workers, consecutive requests from one user may land on different processes. - **State goes somewhere explicit.** `dcc.Store` keeps JSON in the browser (`memory`, `session` or `local` storage); shared caches go to Redis or a Flask-Caching layer; large data stays in the warehouse. - **Layout is arbitrary.** You compose an HTML tree yourself and can drop in CSS or a component library, whereas Streamlit's layout is a constrained set of containers. ## Choosing between them Use Streamlit when the app is essentially a script with knobs — an exploratory tool, a model demo, an internal one-pager — and speed of authoring dominates. It is very hard to beat on time-to-first-working-app. Use Dash when the interaction graph is genuinely a graph: many controls that affect different panels, expensive queries you must not re-run needlessly, a layout that has to look like a product, or a deployment that needs plain horizontal scaling behind a WSGI server. Dash is a Flask app, so authentication middleware, blueprint mounting and existing infrastructure fit. Do not oversell either. Both are Python app frameworks, and neither is a governed BI platform: there is no semantic layer, no row-level security you did not write, no scheduled-refresh subsystem, and no self-service authoring for non-engineers. Every app is code someone has to own, test and upgrade. The honest positioning is that these frameworks win when the interaction is *analytical logic* — a model, a simulation, a bespoke workflow — rather than a slice-and-dice over a star schema, which a BI tool does better and cheaper. ## The performance conversation In Streamlit, the performance question is "what am I re-running that I should be caching?" In Dash, it is "which callbacks fire on this change, and does one of them hit the database when it did not have to?" Both frameworks let you make a chart interaction issue a fresh warehouse query per keystroke; the difference is where you look to find it. A final asymmetry worth naming: Streamlit's rerun makes control flow trivially readable but makes "do this only when that changed" awkward. Dash's graph makes that trivially expressible but makes control flow something you assemble from decorators rather than read down the page. That trade is the answer to the interview question.
- Which model scales more simply across processes, and why?Dash. Its server is stateless — every callback is an independent request — so you run it under gunicorn with several workers behind a plain load balancer and no session affinity. Streamlit holds per-session state on the server over a websocket, so scaling out needs sticky routing and enough memory per concurrent session. That is an operational difference, not a throughput claim.
- Where does shared state live in a Dash app if globals are out?In the browser via dcc.Store, which round-trips JSON with each callback and is naturally per-user; in an external cache such as Redis or Flask-Caching keyed by a session id, for data too big to ship; or nowhere at all — re-query the warehouse and let a cache in front of it absorb the cost.
- When would you reach for neither and use a BI tool instead?When the work is slice-and-dice over a modelled star schema and the audience is non-engineers. A BI platform brings governed metrics, permissions, scheduled refresh and self-service authoring you would otherwise hand-build. Dash and Streamlit earn their place when the interaction is analytical logic — a simulation, a model, a bespoke workflow — not another crosstab.
Streamlit is a spreadsheet that recalculates every cell whenever you type; Dash is one that recalculates only the cells downstream of the one you changed — and hands you the dependency arrows to draw yourself.
saying these in an interview costs you the question
- Says Dash also re-runs the whole script on interaction
- Keeps per-user state in a Python global in a Dash app
- Claims Streamlit cannot scale at all rather than needing sticky sessions
- Treats either framework as a governed BI platform replacement
- Thinks st.session_state and dcc.Store live in the same place