Mapbox style expressions drive data- and zoom-dependent styling. The footguns below are the ones that cost real time.
A ["zoom"] expression may only appear as the top-level input to an
interpolate or step. You cannot nest it deeper — not inside a match/case
branch, not as an inner input to another expression, not in a filter. Violating it
throws "zoom expressions are not supported" (or silently no-ops in some contexts).
To vary by both zoom and data, put the zoom interpolate outermost and a data
expression in each stop output:
// WRONG — zoom nested inside match → error
["match", ["get","kind"], "big", ["interpolate",["linear"],["zoom"], 10,2, 16,6], 1]
// RIGHT — zoom outermost, data expression per stop
["interpolate", ["linear"], ["zoom"],
10, ["match", ["get","kind"], "big", 2, 1],
16, ["match", ["get","kind"], "big", 6, 3]]
step vs interpolateinterpolate — smooth blend between stops (sizes, opacities, colours, widths).
["interpolate",["linear"],["zoom"], 11,3, 17,6]. Use ["exponential",base] for a
perceptually even ramp across many zooms.step — hard jumps at thresholds (discrete tiers, e.g. cluster colour buckets).
["step",["get","point_count"], "#0f0", 10,"#ff0", 100,"#f00"].match vs casematch — switch on one input against literal values (fast, readable). Values can
be arrays to share an output. Last arg is the mandatory default.
["match",["get","class"], ["wood","grass"],"#bcd29c", "#dde4d0"]case — ordered boolean conditions (ranges, compound logic). Last arg is the else.
["case", ["<",["get","h"],100],"#0f0", ["<",["get","h"],500],"#ff0", "#f00"]match only tests equality; the moment you need </>/&&, switch to case.
coalesce for missing dataFields are often absent or typed inconsistently. Guard with coalesce (first non-null)
and to-string/to-number to normalise:
["match", ["to-string", ["coalesce", ["get","Difficulty"], ""]],
"1","#2e7d32", /* … */ "#cc7d45"]
feature-state in expressions (hover/selection)["feature-state","hover"] reads runtime state set by map.setFeatureState — the
canonical hover/highlight pattern (no setData, see
interaction-and-performance.md). Constraints:
filter.generateId:true or promoteId).coalesce, since state is undefined until first set:
["case", ["boolean",["feature-state","hover"],false], "#ff0", "#888"]map.queryRenderedFeatures(point)[0].layer.paint and the GL JS console error messages
name the offending sub-expression. Build complex expressions incrementally — Mapbox
validates the whole tree and a single type mismatch rejects all of it.