skip to content

In DAX, what is the difference between row context and filter context?

level: middleimportance: must knowfreq 85%

answer

  1. one comes from the visual, one from iteration
  2. one decides which rows are visible
  3. the other only points at a current row
  4. a bare aggregate inside SUMX still sees everything
  5. CALCULATE is the bridge between them

basics

~20 s

Filter context is the set of filters restricting which model rows a DAX expression sees; it comes from visuals, slicers and CALCULATE. Row context is a pointer to one current row inside a calculated column or an iterator, and it does not filter anything by itself.

solid answer

~50 s

**Filter context** is the collection of filters active when an expression is evaluated — the matrix row and column headers, slicers, page and report filters, and anything CALCULATE adds. It decides which rows of the model an aggregate like `SUM(Sales[Amount])` actually sums. **Row context** is different: it is "the current row" that exists inside a calculated column or inside an iterator such as SUMX, AVERAGEX, FILTER or ADDCOLUMNS. It lets you reference `Sales[Qty]` and get one value, but it does *not* filter the model — with only a row context, an aggregate still scans the whole filtered table, not just that row. The bridge between them is **context transition**: wrapping an expression in CALCULATE (which happens implicitly when you call a measure inside an iterator) converts the current row context into an equivalent filter context. Almost every confusing DAX result traces back to one of those three facts.

code

dax · 11 lines
dax
-- filter context only: recomputes per visual cell
Total Sales = SUM ( Sales[Amount] )

-- row context, no transition: full total repeated per row
Wrong = SUMX ( Sales, SUM ( Sales[Amount] ) )

-- row context used properly: per-row arithmetic
Revenue = SUMX ( Sales, Sales[Qty] * Sales[Price] )

-- context transition: measure call is wrapped in CALCULATE
Avg Customer Revenue = AVERAGEX ( Customer, [Total Sales] )

go deeper

for a junior

Be able to say that a measure recomputes per visual cell because the filters differ, and that a calculated column is fixed at refresh. Recognise SUMX as something that walks rows one at a time.

for a middle

Explain both contexts precisely, where each originates, and demonstrate that an aggregate inside an iterator still reads the filter context. Naming context transition and what CALCULATE does to a row context is expected here.

for a senior

Diagnose real symptoms from the model: a measure that repeats the grand total on every row, a ranking that ties everywhere, a column that ignores a slicer. Also flag context transition inside a large-fact iterator as a performance problem, not just a correctness one.

for a principal

Frame it as a maintainability standard: models where authors reach for calculated columns to dodge context rules accumulate refresh cost and untestable logic, so the team convention about measures versus columns is worth making explicit.

## The two contexts DAX evaluates in A DAX expression never evaluates in a vacuum. Two independent kinds of context surround it, they come from different places, and they do different things. Getting them apart is the single highest-leverage piece of understanding in the language; nearly every "why is this number wrong" question in a Power BI report resolves to a mix-up between them. ## Filter context: which rows are visible Filter context is a set of filters over model columns that is active while the expression evaluates. It accumulates from several sources at once: the row and column headers of the visual (a matrix cell for `Bikes` / `2024-03` carries both), slicers, visual-level, page-level and report-level filters, cross-filtering from another visual, row-level security, and any filter arguments supplied by CALCULATE. Relationships then propagate those filters from dimension tables to fact tables. When you write `Total Sales = SUM(Sales[Amount])`, the measure does not know or care which visual it is in. It sums `Sales[Amount]` over whatever rows survive the current filter context. That is why the same measure returns a different number in every cell of a matrix: same formula, different filter context per cell. ## Row context: which row is current Row context is created in exactly two situations. First, in a calculated column: DAX walks the table row by row, and inside the expression a bare column reference like `Sales[Qty]` means "the quantity of the row being computed right now". Second, inside an iterator function — `SUMX`, `AVERAGEX`, `MINX`, `MAXX`, `RANKX`, `FILTER`, `ADDCOLUMNS` and friends — which iterate a table expression and evaluate their second argument once per row, with that row as the row context. The crucial property is what row context does **not** do: it does not filter the model. If you write `SUMX ( Sales, SUM ( Sales[Amount] ) )` you do not get a per-row sum. Inside each iteration the inner `SUM` still sees the full filter context — the whole visible Sales table — so you get the grand total repeated once per row and multiplied by the row count. Row context makes a value addressable; it does not narrow anything. A second consequence: row context does not automatically follow relationships. From a row of `Sales` you cannot reference `Product[Category]` directly — you must call `RELATED ( Product[Category] )` to traverse the many-to-one relationship, or `RELATEDTABLE` to go the other way. ## Context transition: the bridge CALCULATE is the function that turns the current row context into filter context. When CALCULATE is evaluated while a row context exists, it takes the current row's values on the iterated table's columns and applies them as filters before evaluating its expression. This is called context transition, and it is what makes the following work: ``` Customer Revenue Rank = RANKX ( ALL ( Customer ), [Total Sales] ) ``` Here `[Total Sales]` is a measure, and every measure reference is implicitly wrapped in CALCULATE. So inside the RANKX iteration over customers, the row context on `Customer` transitions into a filter on that customer, and `[Total Sales]` returns that customer's sales rather than the grand total. Had you inlined `SUM ( Sales[Amount] )` instead of calling the measure, no transition would occur and every customer would rank identically. That implicit wrapping is also the classic performance trap: context transition inside an iterator over a large fact table forces a filter operation per row, and a `SUMX ( Sales, [Total Sales] )` over millions of rows is both wrong and slow. ## Where each one lives A measure evaluated by a visual starts with a filter context and **no** row context. That is why `Margin = Sales[Price] - Sales[Cost]` fails as a measure — there is no current row to read those columns from — while the same expression is perfectly legal as a calculated column, or inside `SUMX ( Sales, Sales[Price] - Sales[Cost] )`, both of which supply a row context. Conversely a calculated column is computed at refresh time under a row context and essentially no report filter context, which is why a calculated column cannot react to a slicer. ## How to answer this in an interview Say what each context is, where it comes from, and one consequence of each: filter context decides which rows an aggregate sees and arrives from the visual; row context identifies a single row and does not filter, so aggregates inside an iterator still see everything unless CALCULATE transitions the context. Then name context transition and give one concrete example — a measure called inside SUMX behaving per row, versus a raw SUM inside SUMX returning the grand total. That progression is exactly the depth an interviewer is probing for.

  • What does SUMX(Sales, SUM(Sales[Amount])) return, and why?
    The grand total of the filtered Sales table multiplied by its row count. SUMX supplies a row context, but `SUM` is an aggregate that reads the filter context, and the row context does not narrow it. Each iteration therefore computes the same full total. Referencing a measure instead of the raw SUM would trigger context transition and give the per-row result.
  • Why does a calculated column ignore a slicer while a measure responds to it?
    A calculated column is materialised at data refresh under a row context, with no report filter context available, so its value is fixed in the model. A measure is evaluated at query time inside whatever filter context the visual, slicers and filters produce, so it recomputes for every cell and every selection.
  • Why does RELATED exist if relationships already propagate filters?
    Relationships propagate *filter* context automatically, but row context does not travel across them. Inside a row context on the fact table there is no current row on the related dimension, so you call RELATED to follow the many-to-one relationship and fetch that row's column value explicitly. RELATEDTABLE handles the one-to-many direction and returns a table.

Filter context is the search box narrowing which records are on screen; row context is the cursor sitting on one of them. Moving the cursor does not change what the search returns — unless something explicitly turns the cursor position into a new search term.

saying these in an interview costs you the question

  • Saying row context filters the model like a WHERE clause
  • Believing SUM inside SUMX aggregates only the current row
  • Expecting a calculated column to respond to a slicer
  • Referencing a bare column in a measure and expecting the current row
  • Claiming filter context and row context are two names for the same thing

context