Historical Context & Motivation
The distinction between row context and filter context did not emerge from thin air; it is a consequence of decades of evolution in analytical query languages and the specific engineering decisions made when Microsoft designed DAX (Data Analysis Expressions). Before DAX, business intelligence tools relied on SQL, MDX, or spreadsheet formulas — each with its own notion of 'which data am I currently looking at?' SQL operates row-by-row in its WHERE clauses and aggregates via GROUP BY, while MDX introduced the concept of multidimensional cubes with tuple-based coordinates. DAX inherited ideas from both worlds and crystallized them into exactly two evaluation contexts, creating a model that is elegant yet initially confusing for newcomers.
The central question DAX forces you to answer is deceptively simple: "When this expression evaluates, which row is it looking at, and which subset of the table has been sliced away?" These two independent mechanisms — knowing the current row, and knowing the current filter — interact, overlap, and sometimes transform into each other. Failing to distinguish them is the root cause of most DAX bugs.
Core Principles & Definitions
Every DAX expression executes within an evaluation context — the combination of environmental information the engine uses to resolve column references and compute results. This evaluation context is composed of two independent components that may be present simultaneously, independently, or not at all. Understanding their orthogonal nature is essential before examining any formula.
Row Context
Filter Context
Context Transition
No Context ≠ All Data
Visual Explanation — The Two Contexts in Action
The diagram below illustrates how row context and filter context operate on a simple Sales table. On the left, filter context (shown as a funnel) reduces the table to only those rows matching active slicer selections. On the right, within the surviving rows, an iterator such as SUMX introduces a row context that walks through each row one at a time, evaluating a per-row expression before aggregation.
Notice how the two contexts are layered, not alternatives. The filter context acts first, pruning the data. The row context then operates within that already-filtered subset. This layered composition is exactly why a measure like SUMX(Sales, Sales[Qty] * Sales[Price]) yields different results depending on whether a slicer for 'Region = West' is active: the filter context changes the population of rows the iterator sees, even though the row-level expression remains identical.
How the Engine Resolves Context
Although DAX is not traditionally expressed with mathematical notation, formalizing the evaluation model clarifies how the engine resolves references. Let T be a table with columns c₁, c₂, …, cn. A filter context F is a set of predicates that collectively define a subset T' ⊆ T. A row context R is a binding of each column ci to a specific value vi from a single row r ∈ T'.
Sales[Amount] resolve to the bound value vi of the corresponding column.Where Each Context Originates
Knowing the theoretical definitions is necessary but not sufficient; a practicing DAX developer must be able to identify which context is active for any given expression. The following diagram maps every common DAX construct to the context it creates, revealing patterns that generalize across hundreds of functions.
| Characteristic | Row Context | Filter Context |
|---|---|---|
| Granularity | Exactly one row at a time | A set of rows (zero to all) |
| Default in measures | Not present — must be created by an iterator | Always present (may be empty = all rows) |
| Default in calc columns | Automatically created for each row | Not present beyond initial table load |
| Propagation | Does not propagate across relationships | Propagates via model relationships |
| Modified by | Nesting another iterator (new row context shadows outer) | CALCULATE, CALCULATETABLE, ALL, REMOVEFILTERS |
Worked Example — Revenue by Region
Consider a star-schema model with a Sales fact table (OrderID, ProductID, RegionID, Qty, UnitPrice) and a Regions dimension (RegionID, RegionName). We define a measure Total Revenue and explore how it evaluates in a matrix visual with RegionName on rows and a slicer for Year = 2024.
Total Revenue := SUMX( Sales, Sales[Qty] * Sales[UnitPrice] ). This measure is an iterator: SUMX creates a row context over the Sales table and evaluates the per-row expression Qty × UnitPrice, then sums the results.RegionName on matrix rows and has a slicer set to Year = 2024. When Power BI renders the cell for 'West', the filter context is: Regions[RegionName] = "West" AND Calendar[Year] = 2024. These filters propagate through relationships to the Sales table, leaving only Sales rows that belong to the West region in 2024.Sales[Qty] * Sales[UnitPrice]. Suppose the first three rows yield 5 × 20 = 100, 3 × 50 = 150, and 10 × 10 = 100. This computation uses only the row context — the filter context has already done its work by limiting which rows the iterator sees.SUM(Sales[Amount]) (assuming a pre-computed Amount column), the result would be the same — but for a different reason. SUM is an aggregation function that operates in filter context only; it does not create a row context. SUMX, by contrast, explicitly creates row context so it can compute a row-level expression before aggregating. Choose SUMX when the per-row calculation cannot be pre-materialized in a column.Common Pitfalls & Comparisons
Most DAX errors stem from confusing which context is active. The table below catalogs frequent mistakes alongside their correct counterparts, emphasizing the mental model shift required.
| Mistake | What Actually Happens | Correct Approach |
|---|---|---|
Using a bare column reference (e.g., Sales[Qty]) in a measure without an iterator | DAX implicitly wraps it in an aggregation (SUM by default in some tools) or throws an error — no row context exists to resolve it. | Wrap in an aggregation: SUM(Sales[Qty]) or use SUMX to create explicit row context. |
| Expecting CALCULATE to 'loop' over rows | CALCULATE modifies filter context, not row context. It does not iterate. | Use an iterator (SUMX, FILTER) to create a row-by-row loop, then CALCULATE inside if you need to adjust the filter. |
| Calling a measure inside SUMX without understanding context transition | The measure's implicit CALCULATE converts the row context into a filter context, which may match multiple rows if the row is not unique. | Ensure the iterated table has a unique key, or use the raw column expressions instead of a measure reference. |
| Assuming a calculated column has filter context from slicers | Calculated columns are evaluated once at model refresh, with a row context but no slicer-driven filter context. | Use a measure instead if the result should respond to slicer selections at query time. |
Connection to Advanced DAX Patterns
Once the two-context model is internalized, you unlock the ability to reason about sophisticated DAX patterns — from time intelligence to virtual relationships. Every advanced pattern is, at its core, a deliberate manipulation of filter context or row context (or both). The table below maps core concepts to their advanced extensions.
| Foundational Concept | Advanced Extension | Context Mechanism |
|---|---|---|
| Filter context from slicers | Time intelligence (TOTALYTD, SAMEPERIODLASTYEAR) | CALCULATE replaces the date filter with a new set of dates, modifying filter context programmatically. |
| Row context in SUMX | RANKX, PERCENTILEX — statistical iterators | Nested row contexts: the outer iterates the ranking table; the inner (via CALCULATE) evaluates each candidate against a shifted filter. |
| Context transition | Virtual relationships via TREATAS / USERELATIONSHIP | TREATAS injects a column of values as a filter, mimicking a relationship without a physical one in the model. |
| Removing filter context with ALL | Percentage-of-total, market-share calculations | CALCULATE( [Measure], ALL(Dimension) ) removes a filter to compute the denominator across all values of a dimension. |
If you are pursuing optimization or performance tuning with DAX Studio's Server Timings, the dual-context model also explains why certain formulas produce a high number of storage engine queries: each unique combination of filter context values may trigger a separate query against the VertiPaq column store. Understanding this connection between the logical evaluation model and the physical query plan is what separates an intermediate DAX user from an expert.
Practice Problems
Sales[LineTotal] = Sales[Qty] * Sales[Price] and a measure Total Sales := SUM(Sales[LineTotal]) are defined. Which evaluation context does the calculated column use to resolve Sales[Qty]? Which context does the measure use to determine the set of rows it aggregates? Explain why a calculated column's result does not change when a user clicks a slicer.Avg Price := AVERAGEX( Products, Products[ListPrice] ), a Products table with 500 rows, and a slicer set to Category = 'Electronics' (which filters Products to 120 rows), how many times does AVERAGEX's row context activate? What is the table that the iterator scans?Weighted Avg := SUMX( Sales, Sales[Qty] * RELATED(Products[Price]) ) / SUM(Sales[Qty]). Explain where row context and filter context each play a role. Why is RELATED necessary here, and would the formula work if Products[Price] were used without RELATED?Rev % of Total := DIVIDE( [Total Revenue], CALCULATE( [Total Revenue], ALL(Products[Category]) ) ). For the cell at Category = 'Furniture', Year = 2024, describe the filter context for the numerator and the denominator separately. What does ALL(Products[Category]) do to the filter context?RankedProducts = ADDCOLUMNS( Products, "Rank", RANKX( ALL(Products), [Total Revenue] ) ). Inside the RANKX expression, [Total Revenue] is a measure reference. Explain step by step: (a) what context does ADDCOLUMNS provide for each row, (b) what happens when RANKX iterates ALL(Products), (c) how does [Total Revenue] get evaluated inside RANKX — specifically, what context transition occurs, and why does ALL(Products) ensure the ranking covers all products regardless of the outer row context?Lesson Summary
Every DAX expression evaluates within a combination of row context and filter context. Row context is a pointer to a single row, created by calculated columns and iterator functions (SUMX, AVERAGEX, FILTER, ADDCOLUMNS). Filter context is a set of active filters that restrict visible rows, originating from slicers, report filters, visual axes, and the CALCULATE function. The two contexts are orthogonal: filter context narrows the data, then row context (if present) walks through each surviving row.
The bridge between them is context transition: when CALCULATE is invoked (explicitly or via a measure reference) inside a row context, it converts each column binding into a filter predicate. Mastering this interplay — knowing when you have row context, when you have filter context, and when one transforms into the other — is the single most important skill for writing correct, efficient DAX. Common pitfalls include referencing columns without aggregation in measures (no row context), expecting CALCULATE to iterate (it modifies filter context, not row context), and ignoring context transition when calling measures inside iterators.