In Plotly Dash, what is the difference between Input and State in a callback?
answer
- one of the two also pulls the trigger
- the other is only read when asked
- think of a form with a submit button
- edges in the graph versus a lookup
- the button is the trigger, the fields are not
basics
~20 sBoth pass a component property into a Dash callback, but only Input triggers it. State values are read at fire time without causing a fire, which is how you build a form that recomputes on a button click rather than on every keystroke.
solid answer
~50 sIn a Dash callback the decorator lists `Output`, `Input` and `State` dependencies. An `Input` does two jobs: it supplies a value **and** any change to it fires the callback. A `State` only supplies the value — Dash reads its current property when the callback fires but a change to it triggers nothing. The classic use is a form: the text boxes and dropdowns are `State`, the submit button's `n_clicks` is the `Input`, so the expensive query runs once per click instead of on every keystroke. Two related controls matter: `prevent_initial_call=True` stops Dash firing the callback once on page load (otherwise `n_clicks` arrives as `None` and you have to guard for it), and raising `dash.exceptions.PreventUpdate` aborts a run so no output is written. All three kinds are positional — `State` arguments come after the `Input` ones in the function signature.
go deeper
Remember the one-line distinction: both pass values in, only one of them fires the callback. Be able to point at the button in a form example and say which dependency type it is.
Explain the graph model — Input dependencies are edges that determine when and in what order callbacks run, State is just a read — and demonstrate prevent_initial_call and PreventUpdate.
Show you have debugged a real callback graph: stale State inside a chain, callbacks firing more often than the data source can take, and using the callback context to branch on which control the user actually touched.
Own the interaction contract for a whole app: which surfaces recompute live and which require an explicit run, what that costs the warehouse behind it, and how the team keeps the callback graph from becoming untraceable.
## Three dependency types A Dash callback decorator declares three kinds of dependency: - **`Output(id, property)`** — what the callback writes. - **`Input(id, property)`** — a value the callback reads, *and* a trigger: whenever that property changes in the browser, the callback runs. - **`State(id, property)`** — a value the callback reads, and nothing more. Changing it does not run anything. That is the entire distinction, and it is the first real design decision in a Dash app: which controls should recompute immediately, and which should merely be *available* when something else recomputes. ## The form pattern Without `State`, every widget is a trigger. A form with a date range, three dropdowns and a free-text filter would fire the callback on each individual change — five queries to answer one question, with the intermediate ones running against a half-filled form. The fix is to make the widgets `State` and the button the `Input`: ``` @callback( Output("results", "data"), Input("run-btn", "n_clicks"), State("start-date", "date"), State("end-date", "date"), State("region", "value"), prevent_initial_call=True, ) def run_query(n_clicks, start, end, region): ... ``` The user fills the form freely; nothing happens until the button's `n_clicks` increments. Note what `n_clicks` is: an integer that Dash increments on each click. You never compare it to a specific number — you just use the *change* as the trigger. ## prevent_initial_call and PreventUpdate By default Dash fires every callback once when the page loads, so the app renders a complete initial state. For a button-driven callback that is wrong: on load, `n_clicks` is `None` and the form is empty, so the query runs against nothing. Two mechanisms handle it. `prevent_initial_call=True` on the callback suppresses that initial firing entirely. It is the declarative fix and the one to reach for first. `raise dash.exceptions.PreventUpdate` inside the function aborts the run: no output is written and dependent callbacks do not fire. Use it for conditional aborts — the input arrived empty, the user has not selected enough options, nothing actually changed. The older idiom `if n_clicks is None: raise PreventUpdate` predates `prevent_initial_call` and still appears in codebases. A third tool sits alongside them: `dash.no_update`, returned *in place of a value* for one output of a multi-output callback, leaves just that property untouched while the others update. `PreventUpdate` is all-or-nothing; `no_update` is per-output. ## Chained callbacks Because an `Output` of one callback can be the `Input` of another, Dash chains them: pick a country, which updates a city dropdown's `options`, whose `value` change then updates a chart. Dash resolves this order on the client from the callback graph, and the browser shows a loading state for the components still waiting. This is where `State` gets subtle. If a chained callback reads a value as `State` that an earlier callback in the same chain is about to change, you may read the stale value — Dash guarantees ordering for `Input` dependencies, which are edges in the graph, but a `State` is not an edge. If ordering matters, it must be an `Input`. ## Knowing which input fired A callback with several `Input`s needs to know which one triggered this run. `dash.ctx` (the callback context, also available as `dash.callback_context`) exposes `ctx.triggered_id`, the id of the component that fired, and `ctx.triggered`, the full list of changed properties. On the initial call — if you have not prevented it — nothing has triggered, and `triggered_id` is `None`, which is the standard way to detect page load. ## The mental model to state in an interview Say it as a graph: `Input` edges define *when* the callback runs; `State` is a lookup performed at run time. Then give the button-form example, mention `prevent_initial_call=True` for the initial firing, and `ctx.triggered_id` for disambiguating multiple inputs. That covers what an interviewer is checking — that you have actually built a Dash form and hit the every-keystroke problem.
- Your button-driven callback still runs once when the page loads. Why, and what are the two fixes?Dash fires every callback once on load to build the initial state, so the button's n_clicks arrives as None. Either set `prevent_initial_call=True` on the callback, which suppresses that firing declaratively, or guard inside the function with `if not n_clicks: raise PreventUpdate`. The first is preferred; the second predates it and still appears in older code.
- With three Inputs on one callback, how do you tell which one fired?Read the callback context: `dash.ctx.triggered_id` gives the id of the component that triggered this run, and `ctx.triggered` gives the full list of changed properties. On the initial call nothing has triggered, so `triggered_id` is None — a common way to branch on page load. Use it instead of comparing old and new values yourself.
- When is State a trap in a chain of callbacks?When you read as State a value that an earlier callback in the same chain is about to change. Dash orders execution using Input edges in the callback graph; State is not an edge, so it is read as it currently stands and may be stale. If the ordering matters, promote it to an Input.
saying these in an interview costs you the question
- Thinks State also triggers the callback, just later
- Wires every form field as an Input and fires a query per keystroke
- Compares n_clicks to a specific number instead of using the change
- Uses State for a value whose change must reorder the callback chain
- Never guards the initial call, then debugs a None argument