Calculated columns
A calculated column is computed at refresh, row by row, and stored in the model. It has row context — it knows which row it's on, so [Price] * [Quantity] works directly.
Because it's stored, it consumes memory and increases model size. Use one when you need a value to slice, filter, group, or put on an axis — something that behaves like a real column.
Measures
A measure is computed at query time, in the filter context created by the visual. It has no row context, so it must aggregate: SUM(Sales[Amount]), not Sales[Amount].
Measures store nothing. They cost CPU when a visual renders, not memory. Use one for anything you'd put in the Values well of a visual.
The default choice
Prefer measures. They're flexible, respond to filter context, and don't inflate the model.
Reach for a calculated column only when you genuinely need a static per-row attribute to slice by — a bucket label, a flag, a concatenated key. And if you need one, ask whether it belongs in Power Query instead, where it's computed once at the source rather than by the DAX engine.
Iterators bridge the gap
Sometimes you need per-row arithmetic inside a measure. That's what the X functions are for:
Revenue = SUMX(Sales, Sales[Quantity] * Sales[Price])
SUMX iterates the table, creates row context for each row, evaluates the expression, then sums the results. This gives you row-level logic without storing a column — usually the better answer than a calculated column plus SUM.