What is the most common DAX mistake slowing down Power BI reports?
The single most common DAX performance mistake is wrapping filter conditions inside a FILTER function when writing CALCULATE expressions. It produces correct numbers, so it goes unnoticed until the report hits production data at scale. At that point, measures that took milliseconds on a sample dataset start taking seconds on a table with one million rows.
FILTER is a valid DAX function. The problem is using it by default for simple column comparisons when a faster, cleaner alternative already exists. This pattern appears in almost every Power BI model Kernel Flow audits, and it is one of the easiest performance issues to fix.
Why does using FILTER inside CALCULATE hurt Power BI performance?
FILTER is an iterator. When placed inside CALCULATE, it walks through every single row in the target table and evaluates the condition on each one. On a Sales table with one million rows, that is one million individual evaluations. The engine then builds a new in-memory table of matching rows before passing it back to CALCULATE as a filter.
Compare these two measures that produce identical results:
Slow pattern (FILTER iterator): Red Sales = CALCULATE(COUNTROWS(Sales), FILTER(Sales, Sales[Colour] = "Red")) forces the engine to materialise the table, iterate every row, build a filtered table, then apply it to the filter context.
Fast pattern (boolean expression): Red Sales = CALCULATE(COUNTROWS(Sales), Sales[Colour] = "Red") tells VertiPaq to look up "Red" directly from the column index, skipping iteration entirely and resolving in a fraction of the time.
On small tables the difference is invisible. On tables with millions of rows, or across a report page with 15 visuals all using similar measures, the gap between milliseconds and seconds compounds into a report that feels broken.
How does Power BI's VertiPaq engine handle boolean filter expressions?
When a boolean expression is passed directly to CALCULATE, VertiPaq modifies the filter context by adjusting its internal bitmap. No intermediate table is created. The engine uses the column index built at data load time to identify matching values instantly. This is exactly the operation VertiPaq's columnar storage is designed for.
When FILTER is passed instead, the engine runs four steps before any calculation happens: materialise the source table in memory, iterate each row, evaluate the condition, and build a new filtered table. For simple column comparisons using equals, greater than, less than, or IN operators, a boolean expression skips all four of those steps.
When should you actually use FILTER in Power BI DAX?
FILTER is the right choice in three specific scenarios. Outside of these, a direct boolean expression is faster and cleaner.
Cross-table comparisons: Boolean filter expressions inside CALCULATE reference a single column from a single table. When filter logic compares values across two tables, such as filtering a Date table based on a measure from a Sales table, FILTER provides the row context needed to make that evaluation work.
Measure-dependent filters: Measures cannot be used in boolean filter expressions. If the filter condition depends on a calculated measure, such as [Total Sales] > [Average Sales], FILTER is the only option because it evaluates each row against the measure result individually.
Complex row-level logic: When a condition requires row-by-row evaluation against logic that cannot be expressed as a single column comparison, FILTER creates the row context necessary for that evaluation to work correctly.
What is KEEPFILTERS and when does it matter in Power BI?
Boolean filter expressions inside CALCULATE replace the existing filter context on that column. If a slicer filters Sales[Colour] to Blue and Green, and the measure applies Sales[Colour] = "Red", the measure overrides the slicer and returns Red regardless of what the user selected. This is a common source of unexpected results in live reports.
KEEPFILTERS solves this. Wrapping the boolean expression in KEEPFILTERS preserves the existing slicer context while still using the fast boolean evaluation path. The measure only returns Red rows that are also visible within the current filter context. This delivers both performance and correct, context-aware results.
Use boolean expressions: For straightforward column filters using equals, greater than, less than, or IN operators, always pass a boolean expression directly to CALCULATE instead of wrapping it in FILTER.
Use KEEPFILTERS: When the measure must respect an active slicer or existing filter context on the same column, wrap the boolean expression in KEEPFILTERS to preserve context without sacrificing performance.
Use FILTER: Reserve FILTER for cross-table logic, measure-dependent conditions, or complex row-level evaluations that require iteration to resolve correctly.
How do Kernel Flow Power BI audits identify and fix DAX performance issues?
Kernel Flow audits Power BI models across wholesale, manufacturing, insurance, and professional services businesses running Microsoft 365 and tools like SAP or Salesforce alongside Power BI. DAX performance issues like overuse of FILTER appear in nearly every model reviewed, regardless of the team's experience level.
The fix is systematic. Each CALCULATE expression is reviewed to determine whether the filter argument is a simple column comparison. Where it is, the FILTER wrapper is removed and replaced with a direct boolean expression or a KEEPFILTERS wrapper where context preservation is required. The result is faster report load times, lower memory usage, and models that stay performant as data volumes grow.
