Unreadable DAX is a technical debt bomb waiting to go off. This lesson teaches you the indentation patterns, naming conventions, and commenting strategies that make your measures easy to debug, hand off, and extend — starting from first principles.

Picture this: you inherit a Power BI report from a colleague who left the company six months ago. You open the measure editor and find this:
Metric=IF(ISBLANK(DIVIDE(CALCULATE(SUM(Sales[Revenue]),DATEYTD(TODAY(),'Calendar'[Date])),CALCULATE(SUM(Budget[Amount]),DATEYTD(TODAY(),'Calendar'[Date])))-1,0,DIVIDE(CALCULATE(SUM(Sales[Revenue]),DATEYTD(TODAY(),'Calendar'[Date])),CALCULATE(SUM(Budget[Amount]),DATEYTD(TODAY(),'Calendar'[Date])))-1)
What does this measure do? Is it a sales ratio? A budget variance? A performance index? You genuinely cannot tell without spending 15 minutes untangling it — and that's assuming there are no bugs hidden in the nesting. This is the DAX formatting problem in its purest form: technically valid code that is practically unreadable.
Good DAX formatting is not a cosmetic concern. It's an engineering discipline. When your measures are consistently structured, properly named, and thoughtfully commented, you can debug them faster, hand them off confidently, and extend them without accidentally breaking something. By the end of this lesson, you'll be able to write DAX that your future self — and your colleagues — will actually thank you for.
What you'll learn:
VAR / RETURN blocks to dramatically improve readabilityThis lesson assumes you're comfortable writing basic DAX measures in Power BI Desktop. You should understand what a measure is and how it differs from a calculated column — if you need a refresher, read DAX Fundamentals: When to Use Calculated Columns vs Measures in Power BI first. You should also have a general idea of how functions like SUM, CALCULATE, and IF work.
Let's get one myth out of the way: DAX does not care about whitespace. You can write a measure on one line or spread it across fifty lines, and Power BI will evaluate it identically. The formatting exists entirely for humans — for you, your teammates, and whoever inherits this report next year.
There's a deeper reason formatting matters, though. The act of formatting your code forces you to understand it. When you manually indent a nested CALCULATE call and realize you need four closing parentheses at the end, you often catch a bug you'd otherwise have missed. Formatting is a thinking tool, not just a presentation tool.
Key insight: Professional DAX developers spend as much time reading code as writing it. Formatting is an investment in that future reading — yours and everyone else's.
Consider a realistic scenario: a finance analyst has built 40 measures for a P&L report. Six months later, the CFO wants to change how budget variance is calculated. If those 40 measures are unformatted blobs, every edit is a liability. If they're cleanly structured, the change takes an hour.
The single most impactful habit you can build is placing each function argument on its own line, indented one level from its parent function. This applies everywhere: CALCULATE, IF, DIVIDE, SUMX, nested FILTER calls — everywhere.
Here's a simple CALCULATE measure written flat:
Revenue YTD = CALCULATE(SUM(Sales[Revenue]),DATESYTD('Calendar'[Date]))
And here's the same measure formatted:
Revenue YTD =
CALCULATE(
SUM( Sales[Revenue] ),
DATESYTD( 'Calendar'[Date] )
)
That's it. Two arguments, two lines, one level of indentation. The function name sits on the line above its arguments, and the closing parenthesis returns to the indentation level of the function name. This is the foundational pattern — everything else builds on it.
Now let's look at nesting. When one function is an argument to another, it gets indented further:
High Value Revenue =
CALCULATE(
SUM( Sales[Revenue] ),
FILTER(
Sales,
Sales[OrderAmount] > 10000
)
)
Notice how FILTER is indented as an argument of CALCULATE, and its own arguments (Sales and the condition) are indented one further level. The closing parentheses stack up cleanly, each returning to the level of its opening function. Once you train your eye to read this pattern, the logical structure becomes immediately visible.
Tip: Use the free tool DAX Formatter (daxformatter.com) to instantly apply consistent indentation to any measure you paste in. It's not perfect for every situation, but it handles 90% of the grunt work and is a great starting point when you're cleaning up inherited code.
A well-named measure is half the documentation. A poorly named one forces anyone reading it to reverse-engineer your intent.
Measure names should be descriptive noun phrases that tell you what the number represents — not how it's calculated. Here are some patterns that work well in practice:
| Instead of this | Use this |
|---|---|
Metric |
Revenue Variance % |
Calc1 |
Gross Margin $ |
M_Sales |
Sales Amount |
BudVarYTD |
Budget Variance YTD |
x |
Customer Retention Rate |
Notice a few conventions in the "Use this" column:
$, %, #, or Qty when it removes ambiguity. Gross Margin is ambiguous — is it dollars or percent? Gross Margin $ and Gross Margin % are not.Revenue YTD, Revenue MTD, Revenue Prior Year makes a measure table scannable at a glance.When you use VAR blocks — and you should be using them heavily, as we'll cover in the next section — your variable names follow a different convention. Since they live inside the measure and never surface in the UI, you can use camelCase or PascalCase without worrying about how they look to end users:
Revenue vs Budget % =
VAR CurrentRevenue = [Revenue YTD]
VAR CurrentBudget = [Budget YTD]
VAR VarianceAmount = CurrentRevenue - CurrentBudget
RETURN
DIVIDE( VarianceAmount, CurrentBudget )
Each variable name reads like a sentence fragment that completes the thought "this stores the..." — CurrentRevenue stores the current revenue, VarianceAmount stores the variance amount. That clarity is the goal. If you'd like to go deeper on how variables improve both readability and performance, DAX Variables in Practice: Using VAR and RETURN to Simplify Complex Measures covers this in full detail.
Always use the full TableName[ColumnName] reference form for columns inside your measures. Avoid the shorthand [ColumnName] alone — it works in some contexts but creates ambiguity in others and makes it harder to understand which table a column belongs to when you're reading the code cold.
-- Avoid this:
Total Orders = COUNT( [OrderID] )
-- Prefer this:
Total Orders = COUNT( Sales[OrderID] )
Warning: The shorthand
[ColumnName]without a table prefix only works safely when there's no ambiguity about row context. In iterator functions likeSUMXor insideFILTER, always use the fully qualified reference to avoid subtle bugs that are very hard to trace.
Variables are the single most powerful readability tool in DAX. They let you name intermediate calculations, avoid repeating expressions, and break a complex measure into a readable narrative. If you're writing a measure that has the same sub-expression appearing more than once, that's your immediate signal to extract it into a variable.
Let's take that unreadable measure from the introduction and rebuild it properly:
-- Before: unreadable wall of text
Metric=IF(ISBLANK(DIVIDE(CALCULATE(SUM(Sales[Revenue]),DATEYTD(TODAY(),'Calendar'[Date])),CALCULATE(SUM(Budget[Amount]),DATEYTD(TODAY(),'Calendar'[Date])))-1,0,DIVIDE(CALCULATE(SUM(Sales[Revenue]),DATEYTD(TODAY(),'Calendar'[Date])),CALCULATE(SUM(Budget[Amount]),DATEYTD(TODAY(),'Calendar'[Date])))-1)
-- After: readable, maintainable, debuggable
Revenue vs Budget Variance % =
VAR RevenueYTD =
CALCULATE(
SUM( Sales[Revenue] ),
DATESYTD( 'Calendar'[Date] )
)
VAR BudgetYTD =
CALCULATE(
SUM( Budget[Amount] ),
DATESYTD( 'Calendar'[Date] )
)
VAR VariancePct =
DIVIDE( RevenueYTD, BudgetYTD ) - 1
RETURN
IF( ISBLANK( VariancePct ), 0, VariancePct )
These two measures produce identical results. But the second one tells you at a glance: it calculates year-to-date revenue, year-to-date budget, computes the variance percentage, and returns 0 if the result is blank. That's a complete story, readable in under a minute.
Notice the structure: each VAR gets its own block, with the assigned expression indented beneath it if it spans multiple lines. The RETURN keyword stands alone on its own line, signaling the transition from "building blocks" to "output." That's a convention worth adopting universally — it creates a visual checkpoint in every measure you write.
Key insight: Variables in DAX are also evaluated only once, regardless of how many times you reference them. So extracting repeated sub-expressions into variables improves performance as well as readability — a genuine win on both fronts.
DAX supports two comment styles:
-- (double dash). Everything after the dashes on that line is ignored./* ... */. Can span multiple lines.Both are invisible to the DAX engine. They exist purely for readers.
The hardest skill in writing comments is knowing what not to comment. A comment that restates what the code already says is noise:
-- Calculates the sum of revenue (don't write comments like this)
VAR RevenueYTD = CALCULATE( SUM( Sales[Revenue] ), DATESYTD( 'Calendar'[Date] ) )
The code already tells you it's calculating the sum of revenue. The comment adds nothing. Instead, write comments that explain why a decision was made, or what business rule the code encodes:
-- Using DATESYTD with no second argument defaults the year-end to Dec 31.
-- For fiscal year reports, pass the fiscal year end date as the second argument.
VAR RevenueYTD =
CALCULATE(
SUM( Sales[Revenue] ),
DATESYTD( 'Calendar'[Date] )
)
Now the comment is earning its place. It warns future readers about a non-obvious behavior and tells them what to change if business requirements shift.
Here's a realistic example of a fully commented measure done well:
/*
Gross Margin % — Rolling 3-Month Average
Business rule: Marketing uses a 3-month rolling average rather than
point-in-time margin to smooth out seasonal promotions. This was
aligned with the CFO in Q3 2024 planning cycle.
Dependencies: Requires 'Calendar'[Date] to be marked as a date table.
*/
Gross Margin % (3M Avg) =
VAR GrossMarginCurrent =
-- Margin in the current filter context
DIVIDE(
SUM( Sales[Revenue] ) - SUM( Sales[COGS] ),
SUM( Sales[Revenue] )
)
VAR GrossMarginPrior2M =
-- Expand context to prior 2 months and average across all 3
CALCULATE(
AVERAGEX(
VALUES( 'Calendar'[MonthKey] ),
DIVIDE(
SUM( Sales[Revenue] ) - SUM( Sales[COGS] ),
SUM( Sales[Revenue] )
)
),
DATESINPERIOD( 'Calendar'[Date], LASTDATE( 'Calendar'[Date] ), -3, MONTH )
)
RETURN
-- Return 3M average; fall back to current-period margin if history unavailable
IF( ISBLANK( GrossMarginPrior2M ), GrossMarginCurrent, GrossMarginPrior2M )
The block comment at the top serves as a header: it names the measure, explains the business rule, and records a decision. The inline comments explain non-obvious technical choices. The variable names do the rest of the work.
Tip: Make it a habit to include a brief header comment on any measure that encodes a business rule, uses a non-obvious technique, or was the subject of a stakeholder conversation. Future-you debugging at 5 PM on a Friday will be grateful.
Formatting is not just about individual measures — it's also about how measures are organized in your model. A few conventions make a large model navigable:
Use a dedicated measures table. Create an empty calculated table or a manually entered table named _Measures (the underscore sorts it to the top of the fields pane). Put all your measures in it. This keeps them separate from dimension and fact tables and makes them instantly findable.
Group measures into display folders. In Power BI Desktop, you can assign a measure to a display folder in the Properties pane. Use folders like Revenue, Cost & Margin, YTD Comparisons, Budget Variance, and Customer Metrics to create a self-organizing hierarchy. When a report has 60+ measures, folders are the difference between navigable and chaotic.
Prefix measure names by category in large models. In very large models where display folders aren't enough, some teams prefix measure names: REV_ for revenue measures, MARG_ for margin, CUST_ for customer metrics. This is a stylistic choice — use it only if it genuinely helps your team's workflow.
Note: The naming prefix approach can conflict with the "natural language names" principle if measures surface directly in report tooltips. Weigh the tradeoff for your specific audience — internal analytics teams often prefer the prefix; executive dashboards usually benefit from cleaner names without it.
Let's put everything together. Below is a poorly formatted, poorly named measure. Your task is to reformat it from scratch using the conventions from this lesson.
Starting point:
x=IF(OR(ISBLANK(CALCULATE(SUM(Sales[Revenue]),FILTER(ALL(Sales),Sales[Region]=SELECTEDVALUE(Sales[Region])))),CALCULATE(SUM(Sales[Revenue]),FILTER(ALL(Sales),Sales[Region]=SELECTEDVALUE(Sales[Region])))<0),BLANK(),CALCULATE(SUM(Sales[Revenue]),FILTER(ALL(Sales),Sales[Region]=SELECTEDVALUE(Sales[Region]))))
Step 1: Identify the repeated sub-expression. (Hint: CALCULATE(SUM(Sales[Revenue]),FILTER(ALL(Sales),Sales[Region]=SELECTEDVALUE(Sales[Region]))) appears three times.)
Step 2: Extract it into a variable with a clear name.
Step 3: Format the IF / OR logic using the one-argument-per-line pattern.
Step 4: Add a one-line comment explaining the business rule (the measure returns BLANK when the selected region has zero or negative revenue).
Step 5: Name the measure something descriptive — this is a filtered revenue figure for the selected region, with a blank guard for negative values.
Here's one clean solution:
-- Returns BLANK for regions with zero or negative revenue to suppress
-- them from ranked visuals. Confirmed requirement from Sales Director.
Region Revenue (Positive Only) =
VAR SelectedRegionRevenue =
CALCULATE(
SUM( Sales[Revenue] ),
FILTER(
ALL( Sales ),
Sales[Region] = SELECTEDVALUE( Sales[Region] )
)
)
RETURN
IF(
OR(
ISBLANK( SelectedRegionRevenue ),
SelectedRegionRevenue < 0
),
BLANK(),
SelectedRegionRevenue
)
Three references became one. The logic is visible at a glance. The business rule is documented.
Mistake: Over-commenting the obvious. New DAX writers often comment every line to "be safe." This creates visual clutter that actually makes code harder to read. Reserve comments for business rules, non-obvious function behaviors, and architectural decisions.
Mistake: Inconsistent indentation levels. Mixing 2-space and 4-space indentation within the same model is surprisingly disorienting. Pick a standard — most DAX developers use 4 spaces — and stick to it across every measure.
Mistake: Generic variable names inside complex measures. Names like VAR Result or VAR Temp are only marginally better than no variables at all. Be specific: VAR BaselineSalesAmount, VAR FilteredCustomerCount.
Mistake: Forgetting to update comments when the logic changes. A comment that no longer matches the code is worse than no comment — it actively misleads readers. When you update a measure's logic, update its comments in the same edit.
Mistake: Putting all measures directly on fact or dimension tables. This makes the Fields pane harder to navigate and can obscure the purpose of individual measures. Use a dedicated measures table from the start.
Warning: DAX Formatter and similar tools apply stylistic rules that don't always match every team's preferences. Treat auto-formatted output as a starting point, not a final product. Always review auto-formatted measures before committing them to a shared model.
Formatting is the first act of documentation. When you name a measure Revenue vs Budget Variance % instead of Metric, you've already answered the most important question anyone will ask about it. When you indent your CALCULATE arguments and extract repeated expressions into named variables, you make bugs visible and logic auditable. When you add a comment that explains why a business rule exists, you transfer context that would otherwise vanish when you leave the room.
The patterns in this lesson are not optional polish for senior developers. They're foundational habits you should build from the very first measure you write. The cost of applying them is low — a few extra keystrokes and thirty seconds of thought. The cost of ignoring them compounds every time someone has to read your work.
Here's a quick checklist to carry into your next Power BI session:
VAR / RETURN structure for any measure with more than one stepTable[Column] references_Measures table with display foldersFrom here, good next steps include exploring how formatting intersects with more advanced patterns. Since CALCULATE appears in virtually every complex measure, understanding Understanding DAX: CALCULATE and Filter Context deeply will help you format and reason about your most common construct. If you're building time intelligence measures — the kind that appear in almost every business report — Time Intelligence in DAX: YTD, MTD, Previous Period, and Rolling Averages will give you well-structured templates to work from. And when your measures grow in complexity toward financial reporting scenarios, Advanced DAX Patterns for Financial Reporting: Mastering P&L, Balance Sheet, and Budget Models shows how professional teams organize large measure libraries in production models.
Write readable code. Your future self is counting on it.