# Dash Callbacks Not Firing or Firing in a Loop: Dependency Rules, Circular Callbacks, prevent_initial_call

Why a Plotly Dash callback never fires (State does not fire; check Input wiring), why it fires too often (any Input change fires; branch on ctx.triggered_id), why it fires on load (prevent_initial_call=True), and how to break CircularDependency errors (merge synced props into one callback or split flows through a store). Sharing an Output via allow_duplicate=True, and the serverside-vs-clientside latency tradeoff: when the clientside_callback wins and when the server roundtrip is unavoidable.

Exact reference: {"kind":"skill_version","skill_id":"skl_Hb8GW6jNU1ohVhnC86rOng","version_id":"skv_v482mY9JTwLlzJsItZEv_g"}

Applicability: [{"constraint":">=2.0","technology":"Plotly Dash","version_scheme":"semver"},{"constraint":">=2.9 for allow_duplicate=True on shared outputs","technology":"Plotly Dash","version_scheme":"semver"}]

# Dash Callback Firing Rules: Why Callbacks Don't Fire, Fire Too Often, or Loop

A field guide to the mechanics of `@callback` / `@app.callback` dependency wiring:
when a callback fires, why it silently doesn't, how to stop the initial fire,
how to break circular dependencies, and when a clientside callback beats a
serverside roundtrip.

## The one rule that explains most problems

A callback fires when **any `Input` value changes**, plus once when the page or
layout first loads. `State` values are passed to the function but **never fire
it**. That asymmetry is the source of most "callback not firing" reports.

```python
from dash import Dash, Input, Output, State, callback

app = Dash()

@callback(
    Output("result", "children"),
    Input("submit-btn", "n_clicks"),   # fires when this changes
    State("name", "value"),            # read, but never fires
    prevent_initial_call=True,
)
def submit(n_clicks, name):
    return f"Hello, {name}!"
```

If you declared the button as `State` and the text field as `Input`, the
callback would fire on every keystroke and ignore the button. When a callback
"does nothing", check the wiring first: the property that changed must be an
`Input`.

Function argument order follows declaration order: all `Input`s first, then
all `State`s. The decorator groups them positionally, so keep the order
aligned with the function signature.

## Gotcha 1: Every callback fires once on page load

On load, every callback fires with initial values (e.g. `n_clicks` is `None`,
dropdown values are their defaults). This is why you see code like:

```python
from dash.exceptions import PreventUpdate

@callback(
    Output("result", "children"),
    Input("submit-btn", "n_clicks"),
)
def submit(n_clicks):
    if n_clicks is None:
        raise PreventUpdate
    ...
```

`prevent_initial_call=True` removes the need for that guard entirely: the
callback only runs on a genuine `Input` change. The same keyword works on
`clientside_callback`.

App-wide control is available at construction:

```python
app = Dash(prevent_initial_callbacks=True)   # every callback skips the initial fire
```

Individual callbacks can still opt back in with
`prevent_initial_call=False`. There is also the special app-wide value
`"initial_duplicate"`, which prevents initial calls only for callbacks whose
outputs are shared with other callbacks (see the duplicate-outputs section).

Practical note: in apps with dynamically added components, the "initial call"
is not only on page load — a callback also fires when a *new* matching input
is inserted into the layout (for example pattern-matching callbacks firing for
every index when a new indexed button is added). `prevent_initial_call=True`
is the fix for that variant too.

## Gotcha 2: Any Input change fires the whole callback

If a callback has four `Input`s, changing any one of them runs the full
function. This is by design, but it surprises people who assume a callback
only responds to "its" input. Use `ctx.triggered_id` to branch on which input
actually changed:

```python
from dash import ctx

@callback(
    Output("graph", "figure"),
    Output("table", "data"),
    Input("refresh-btn", "n_clicks"),
    Input("filter", "value"),
    prevent_initial_call=True,
)
def update(n_clicks, filter_value):
    triggered = ctx.triggered_id
    if triggered == "refresh-btn":
        return refresh_figure(), refresh_table()
    return filtered_figure(filter_value), filtered_table(filter_value)
```

`ctx.triggered_id` returns the component id (or the id dict for pattern
matching) that caused the current firing. Without it, multi-input callbacks
recompute everything on every change.

## Gotcha 3: Chains cascade — outputs become inputs

When a callback writes to a component property, every callback that lists that
property as an `Input` fires next. This is how multi-step chains work, and it
is also how accidental feedback loops form.

The most common loop shape: callback A updates a property that callback B
reads as an `Input`, and callback B writes a property that callback A reads.
Dash detects dependency cycles at startup and raises a
`CircularDependency` error (you will see `Cycle detected` in the traceback
when the app fails to register the callbacks). Cross-callback cycles are
**not** allowed.

Two legal exceptions, and two fixes:

**Allowed: self-reference in one callback.** A callback may list the *same*
component property as both an `Input` and an `Output`:

```python
@callback(
    Output("counter", "data"),
    Input("counter", "data"),
    Input("add-btn", "n_clicks"),
    prevent_initial_call=True,
)
def increment(current, n_clicks):
    return (current or 0) + 1
```

**Fix 1: merge the cycle into one callback.** If two properties must stay in
sync (for example a URL search string and a date picker), use a single
callback that takes all synced properties as both `Input` and `Output`, then
branch on `ctx.triggered_id` so only the *other* properties get updated:

```python
@callback(
    Output("url", "search"),
    Output("picker", "start_date"),
    Input("url", "search"),
    Input("picker", "start_date"),
    prevent_initial_call=True,
)
def sync(url_search, start_date):
    triggered = ctx.triggered_id
    if triggered == "url":
        return url_search, parse_date_from_search(url_search)
    return build_search_from_date(start_date), start_date
```

Returning the input's own value unchanged avoids re-triggering the syncing
property.

**Fix 2: split unidirectional flows with an intermediate store.** Keep
A-to-B and B-to-A as separate callbacks through a `dcc.Store`, and make each
one no-op unless the store says its side changed (raise `PreventUpdate` when
it is not your turn). This is more code than Fix 1 but scales better when
many properties need syncing.

## Gotcha 4: The same Output used by two callbacks

By default, one component property may be an `Output` of at most one
callback; a second registration raises a duplicate-output error. Since Dash
2.9 you can opt into sharing:

```python
@callback(
    Output("graph", "figure", allow_duplicate=True),
    Input("draw-btn", "n_clicks"),
    prevent_initial_call=True,
)
def draw(n_clicks):
    return build_figure()

@callback(
    Output("graph", "figure"),
    Input("reset-btn", "n_clicks"),
    prevent_initial_call=True,
)
def reset(n_clicks):
    return empty_figure()
```

Rules for `allow_duplicate=True`:
- Every callback sharing the output must set `prevent_initial_call=True`
  (or set the app-wide `prevent_initial_callbacks="initial_duplicate"`), so
  the callbacks don't all fire at once on page load.
- When two such callbacks fire simultaneously, update order is **not
  guaranteed** — don't have them both rewrite overlapping parts of the same
  property.

If the two callbacks instead share an `Input`, that is fine — multiple
callbacks may listen to the same input.

## Serverside vs clientside: the latency tradeoff

A normal Python callback costs a full network roundtrip on every fire: the
browser serializes the `Input`/`State` values, the server runs your function,
and the response travels back. That overhead dominates when the callback:

- moves large payloads (big figures, long tables),
- fires very often (hovering, sliders, rapid keystrokes — a browser allows
  only a handful of concurrent requests, so bursts queue up), or
- sits in a chain where each hop needs another roundtrip.

`clientside_callback` runs the same `Input`/`Output` declaration in the
browser as JavaScript — no request at all:

```python
from dash import clientside_callback, ClientsideFunction, Input, Output

clientside_callback(
    ClientsideFunction(namespace="clientside", function_name="double_value"),
    Output("doubled", "children"),
    Input("number", "value"),
    prevent_initial_call=True,
)
```

with the function defined in `assets/[ANY_JS_FILE]`:

```javascript
window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        double_value: function(value) {
            return value * 2;
        }
    }
});
```

Or pass the JS function as an inline string directly to
`clientside_callback`. Both forms accept `prevent_initial_call=True`.

Decision rule:
- **Clientside** when the transform is cheap, purely presentational (format a
  string, toggle visibility, arithmetic), and needs to respond instantly to
  high-frequency inputs.
- **Serverside** when the callback needs Python libraries, server-side
  globals, secrets, or a database call. A clientside function cannot reach
  anything on the server; if your data lives behind a DB query, the roundtrip
  is unavoidable (cache the query result instead, e.g. with Flask-Caching or
  an in-memory store keyed by the inputs).

## Debugging checklist

1. Callback never fires: is the changing property declared as `Input`, not
   `State`? Does the component id match the layout exactly?
2. Callback fires on load but shouldn't: add `prevent_initial_call=True`.
3. Callback fires too often: check how many `Input`s it has and branch on
   `ctx.triggered_id`.
4. `CircularDependency` at startup: find the cycle; merge into one callback
   with self-referenced Input/Output, or break it with an intermediate store.
5. Duplicate output error: `allow_duplicate=True` plus
   `prevent_initial_call=True`.
6. Noticeable lag on hover/slider/typing: move the cheap transform to a
   clientside callback; keep DB and heavy compute serverside with caching.


## Supporting basis and limitations

Dash docs on clientside callbacks state serverside overhead comes from large payloads, high-frequency calls (network latency/queuing/handshake), and multi-roundtrip chains, and that clientside_callback uses the same Input/Output declaration with a JS function; docs also note clientside callbacks cannot access server globals or DB. Dash docs on duplicate-callback-outputs (Dash 2.9+): allow_duplicate=True requires prevent_initial_call=True or prevent_initial_callbacks="initial_duplicate", and simultaneous update order is not guaranteed. GitHub PR plotly/dash#1228 added prevent_initial_call per callback and app-wide prevent_initial_callbacks (per-callback False override). Forum analysis of circular dependencies documents the Dash rule: cross-callback cycles are rejected (CircularDependency), while the same component property may be both Input and Output of one callback; fixes are merging synced props into one callback with ctx.triggered_id branching, or unidirectional flows through dcc.Store with PreventUpdate guards.

## Change and rationale

New skill covering Dash callback firing mechanics: Input-vs-State wiring rule, per-input firing with ctx.triggered_id branching, prevent_initial_call and app-wide prevent_initial_callbacks including the initial_duplicate mode, breaking CircularDependency cycles via merged single callbacks or intermediate stores, duplicate-output sharing with allow_duplicate=True, and the clientside-vs-serverside latency tradeoff with a decision rule. All code uses real Dash decorator syntax grounded in the official Dash docs and Dash GitHub history.

Callback wiring pain is one of the most reported Dash problems: callbacks that silently never fire (State used as a trigger), callbacks that fire on every page load, CircularDependency startup errors when two properties must stay in sync, and latency from high-frequency serverside roundtrips. Each fix is a small mechanical rule, but the rules are scattered across docs pages and forum threads. One skill collecting the firing rule, the circularity fixes, the initial-call controls, and the clientside tradeoff gives an agent a reliable reference instead of guessing from StackOverflow snippets.
