skip to content

Plotly Dash

Analytical web apps built from Plotly figures and layout components, wired together by callbacks mapping inputs to outputs. Interviewers ask about the callback graph and the stateless server, which is exactly where Dash differs from Streamlit.

on this pageshow

questions

5

In Plotly Dash, how does a component id in app.layout connect to a callback?

level: juniorimportance: must knowfreq 72%

answer

  1. components carry a string label
  2. callbacks point at that label
  3. and at one property of it
  4. the pair is (id, property)
  5. order of arguments matters

basics

~20 s

Each Dash component in the layout carries an id. A callback names that id plus a property — Input('dropdown','value'), Output('chart','figure') — and Dash wires them by matching the id string, so a typo silently means no wiring.

solid answer

~40 s

A Dash app has two halves: `app.layout`, a tree of components (`html.Div`, `dcc.Dropdown`, `dcc.Graph`) where each interactive one gets an `id`, and callbacks, Python functions decorated with `@callback` (or `@app.callback`). The decorator declares `Output("chart", "figure")` and `Input("city", "value")` — an id string plus a property name of that component. Dash serialises the layout to the browser, and when the input property changes the browser posts the new value back; the server runs the function and returns a new value for the output property, which the browser patches in. Arguments are positional and must match the order of the `Input`/`State` declarations. If an id in a callback does not exist in the layout, Dash raises an error on load unless you set `suppress_callback_exceptions=True`, which you need for layouts built dynamically.

go deeper

for a junior

Be ready to write a minimal app from memory: a layout with an id'd dropdown and a dcc.Graph, and one callback with an Output and an Input. Know that the strings must match the ids exactly.

for a middle

Explain the round trip — layout serialised to the browser, property change POSTed back, function run on the server, output property patched in — and why arguments are positional.

for a senior

Show judgment about the callback graph itself: one writer per output, when suppress_callback_exceptions is legitimate versus when it hides a bug, and how you debug an app that fires the wrong callbacks.

for a principal

Own the convention: an id namespace that survives a hundred components, whether pages are separate apps or one callback graph, and how much logic is allowed inside callbacks versus a tested module underneath.

## The two halves of a Dash app A Plotly Dash application is written entirely in Python but runs as a web app. It has exactly two moving parts: 1. **The layout** — `app.layout`, a tree of component objects. `dash.html` mirrors HTML tags (`html.Div`, `html.H1`, `html.Button`); `dash.dcc` ("Dash core components") holds the interactive ones (`dcc.Dropdown`, `dcc.Slider`, `dcc.Graph`, `dcc.Store`, `dcc.Interval`). Each component is a Python object whose keyword arguments become its *properties*. 2. **The callbacks** — plain Python functions decorated with `@callback`, which declare which component properties they read and which they write. The `id` is the glue. It is just a string, unique within the app, that names a component so a callback can point at it. ## How the wiring actually happens When the app starts, Dash serialises the layout tree to JSON and sends it to the browser, where a React front end renders it. It also sends a **callback graph**: a description of every callback's inputs and outputs, expressed as `(id, property)` pairs. When a user changes a dropdown, the React front end sees the `value` property of the component with that id change. It looks up the callback graph, finds every callback with that `(id, property)` as an `Input`, and POSTs the current values to the server's `/_dash-update-component` endpoint. The server runs your Python function, gets a return value, and sends it back; the front end assigns it to the declared `Output` property and re-renders just that component. So the callback signature is a contract: ``` @callback( Output("sales-chart", "figure"), Input("city-dropdown", "value"), ) def update(city): ... ``` `city` receives the dropdown's `value`; the returned object becomes the graph's `figure`. ## Rules that trip people up **Arguments are positional.** The function parameters map to the `Input` and `State` declarations in the order written, not by name. Reordering the decorator's arguments without reordering the function's silently swaps them. (Dash also supports a keyword-style flexible signature, but positional order is the default and the one interviewers ask about.) **Ids must exist.** If a callback references `Output("chart", "figure")` and no component in the layout has `id="chart"`, Dash raises an error when the page loads — a "nonexistent object" error naming the id. This is a feature: it catches typos. If your layout is generated at runtime (multi-page apps, components created inside another callback), set `app = Dash(__name__, suppress_callback_exceptions=True)` so Dash stops validating ids against the initial layout. **One writer per output.** By default a given `(id, property)` may be the `Output` of only one callback; declaring it twice is a duplicate-output error. Later Dash 2.x releases allow `allow_duplicate=True` on an output (paired with `prevent_initial_call=True`) for the cases where two different actions genuinely write the same property. **Properties, not variables.** You never mutate a component object from a callback. `dcc.Dropdown(id="city", options=[...])` in the layout is only the *initial* state; everything after that flows through outputs. Mutating the layout object at request time is a bug — see the statelessness of the server. **Everything crossing the boundary is JSON.** Inputs arrive and outputs leave as JSON, so a callback cannot return a pandas DataFrame or a NumPy array directly; convert it (`df.to_dict("records")` for a `dash_table.DataTable`, a Plotly `Figure` for a `dcc.Graph`, which knows how to serialise itself). ## Why this design Declaring the dependency graph up front — rather than imperatively binding event handlers — is what lets Dash compute, on the client, exactly which callbacks a given change must fire and in what order, chaining them when one callback's output is another's input. It also gives you the callback graph view in the dev tools, which is the fastest way to debug an app that fires too much or not at all.

  • What happens if two callbacks declare the same Output?
    By default Dash raises a duplicate-output error at startup: a component property may have only one writer, which keeps the dependency graph unambiguous. Later Dash 2.x releases let you opt out per output with `allow_duplicate=True`, which must be paired with `prevent_initial_call=True`. The usual fix is still to merge the two callbacks into one that decides what to return.
  • Why would you set suppress_callback_exceptions=True?
    Because Dash validates every callback id against the initial layout on load. If parts of the layout are created later — a multi-page app, or components returned by another callback — those ids do not exist yet and Dash errors. The flag disables that check. The cost is that genuine typos now fail silently at runtime instead of loudly at startup.
  • Can a callback return something other than a component property value?
    No. Every return value is assigned to a declared `Output`'s property and must be JSON-serialisable, so a DataFrame or NumPy array has to be converted first. Two escape hatches exist: `dash.no_update` leaves one output unchanged, and raising `dash.exceptions.PreventUpdate` aborts the whole callback so no output is written.

saying these in an interview costs you the question

  • Thinks callbacks bind by variable name instead of the id string
  • Mutates component objects in the layout from inside a callback
  • Returns a pandas DataFrame straight from a callback
  • Assumes callback arguments match by parameter name, not position
  • Adds suppress_callback_exceptions=True to silence a genuine typo

context

open as a page

In Plotly Dash, what is the difference between Input and State in a callback?

level: middleimportance: must knowfreq 68%

basics

~20 s

Both 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.

open as a page

In Plotly Dash, how does the callback model differ from Streamlit's script rerun?

level: middleimportance: should knowfreq 58%

basics

~20 s

Streamlit 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.

open as a page

Why does a Plotly Dash app that caches a DataFrame in a global break with many users?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Dash callbacks are independent stateless requests. A module-level global is shared by every user of a worker process and absent from the other workers, so users see each other's filtered data or inconsistent results depending on which process answered.

open as a page

In Plotly Dash, how do you stop a 60-second query in a callback from freezing the app?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

Move the work off the request thread with a Dash background callback, which hands the job to a Celery or Diskcache manager and polls for the result. Add a running spec to disable the button and a cancel input, and show progress rather than a frozen page.

open as a page