DAX Prism

DAX Dependency Depth as a Maintainability Smell

DAX Dependency Depth as a Maintainability Smell

Software engineering has a useful concept that Power BI should borrow more often: the code smell. A smell is not a bug. It’s a surface symptom that correlates with deeper problems. A smell cheap to detect, occasionally a false alarm, but reliable enough that when you notice it, you look closer. Long functions smell. So do classes with forty methods. A Power BI report with a tangled 30 table data model and 600 measures, definitely smells as in, there’s probably a better way to build it, but you don’t know until you’ve ordered the test.

Dependency depth, meaning how many layers of measure-on-measure references sit beneath a given measure, is the best maintainability smell a DAX model has. It’s trivially measurable, completely invisible in day-to-day work, and strongly correlated with everything that actually hurts. This post makes the case for treating it that way: not “deep chains are bad,” but depth is the number to watch.

What depth correlates with

A smell earns its keep by predicting problems. Depth predicts four:

Edit blast radius. Every layer beneath a measure is a layer that can change it silently. Edit something at depth one of an eight-layer stack and seven tiers of measures shift. There are no errors, just different numbers on visuals nobody thought to check. The deeper the model runs, the more of it is downstream of any given edit.

Debugging distance. When a top-level number is wrong, the fault can live anywhere in its chain. Depth is a direct measure of how many places you have to look. A wrong value at depth two has two suspects; at depth nine it has nine.

Comprehension cost. Understanding a measure means understanding its stack. A new developer can read a depth-two measure over coffee; a depth-eight measure is an hour of investigation if you’re good. Multiply by every measure a maintainer touches, and depth quietly sets the price of every future change.

Unplanned growth. This is the subtle one. Deliberate architecture tends to produce shallow fan-out meaning many measures resting on a few named foundations. Organic accretion produces depth meaning each developer building on whatever was nearest, one reasonable layer at a time, the way chains actually form. So depth isn’t just a cost in itself; it’s a fingerprint of a model that grew without anyone steering which is unplanned depth as tech debt, detected by its geometry.

When deep is fine: the false alarms

Every smell has false positives, and pretending otherwise turns a useful signal into a dumb rule. Depth’s false alarms are real:

A depth-six chain that runs through deliberate, documented, stable foundationsSalesNet Sales → the time-intelligence tier is architecture, not debt. Each layer is a single source of truth that dozens of measures share; flattening it would trade visible structure for invisible duplication. Time intelligence in particular legitimately adds layers: a YoY measure wrapping a LY measure wrapping a base is three tiers of genuinely distinct, reusable logic.

Which is why the smell test was never “how deep?” It’s “deep through what?” Depth through named, boring, stable foundations is planned. Depth through Custom Measure 3 and something two developers ago called temp is the alarm going off correctly.

Reading depth across a whole model

The concept gets more powerful when you stop asking about one measure and look at the distribution of every measure’s depth, plotted. The shape diagnoses the model:

  • Healthy: most measures shallow, converging on a small set of shared foundations; a short tail of deliberate deep chains (usually time intelligence). Depth exists, and it’s concentrated where it was chosen.
  • Sprawl: everything uniformly shallow — which sounds virtuous and isn’t. No shared foundations usually means the logic is duplicated instead, the opposite smell wearing a disguise.
  • Organic accretion: a long, ragged tail of deep chains through measures nobody can name as foundational. This is the histogram of a model that grew for five years while everyone looked at the visuals.

Three depth distributions: healthy fan-out, duplicated sprawl, organic accretion

One number from this distribution is worth tracking over time: maximum depth, or better, the count of measures beyond your comfort threshold. A model whose deep tail grows quarter over quarter is telling you something no individual code review will.

What to do when it smells

A smell warrants investigation, not reflexive refactoring. The response has three steps:

  1. Pull the tree. For the deep measures, get the actual dependency chain. Remember that depth is only readable from the full graph, which is why this diagnostic barely existed before tools could draw it. DAX Prism generates the trees on demand, which turns “depth distribution” from a nice idea into a number you can pull before and after any cleanup.
  2. Check the foundations. Is the chain running through measures someone chose as foundations? Measures that are named, documented, and stable? Or through accidental load-bearing measures?
  3. Then decide. Two outcomes, both wins: document it as architecture (if the depth is deliberate say so by naming the tiers to explain the depth), or flatten it using the standard tactics meaning VARs for private intermediates, deliberate tiers for shared logic.

The only failure mode is the common one: not looking. Depth is invisible in the formula bar, absent from every report page, and costless right up until the edit that silently changes forty measures. That’s exactly the profile of a problem you want a smoke alarm for.

And that’s the whole argument. Depth is the smoke alarm, not the fire — cheap to check, occasionally crying wolf, and the difference between finding out early and finding out from a stakeholder.