Prepare for Power BI developer interviews with questions grouped by experience level, from dashboards and DAX to enterprise governance.
Junior (0-2 years)
Power BI is Microsoft's business intelligence tool for connecting to data, building reports, and sharing dashboards. It pulls data from sources like Excel, SQL Server, or a cloud service, lets you shape and model that data, then builds visuals on top of it. Companies use it because it turns raw spreadsheets into something a manager can actually glance at and understand in ten seconds.
Power BI Desktop is the free Windows application where you build reports. It's where the modeling, the DAX writing, and the visual layout happen. Power BI Service is the cloud platform where you publish those reports, share them, schedule refreshes, and set up dashboards. You build in Desktop and you distribute through Service.
Three pieces, working together. Power Query handles connecting to and cleaning data before it loads. The data model is where you define relationships between tables and write DAX measures. The report canvas is where you actually build the charts and tables people look at.
A report is a multi-page, interactive document built in Desktop, with filters, drill-downs, and multiple visuals per page. A dashboard lives only in the Service, is a single page, and is built by pinning individual visuals, often from different reports, onto one summary view. Think of a dashboard as a highlight reel and a report as the full game.
Excel files, SQL Server and other relational databases, SharePoint lists, Salesforce, Google Analytics, and plain CSV files are the ones you'll see constantly. Power BI ships with well over a hundred connectors, but in practice most business reporting draws from a database, an Excel export, or a SaaS tool's API.
A workspace is a container for related reports, dashboards, and datasets, shared among a specific group of people. Marketing might have its own workspace, finance another. Content gets published into a workspace, and access to that workspace is what controls who can see it.
A .pbix file. Inside it sits the data model, every Power Query transformation step, all the DAX measures, and the report's visual layout, all bundled into one file. That's why a .pbix can grow large fast once a decent volume of data gets imported into it.
A star schema puts one fact table (sales, transactions) at the center, connected to surrounding dimension tables (date, product, customer) through simple one-to-many relationships. Power BI prefers it because it keeps relationships predictable and DAX calculations behave the way you'd expect. A messy, fully normalized schema with relationships running every direction makes both performance and formula-writing harder than they need to be.
A fact table holds the numbers you're measuring, sales amount, quantity sold, transaction count, and it's usually the biggest table in the model. A dimension table holds the descriptive context around those numbers, like product names, customer details, or dates, and is used to slice and filter the facts.
It describes how rows in one table relate to rows in another. One-to-many is the common case, one row in a Date table matching many rows in a Sales table. One-to-one is rare and usually means two tables should just be merged. Many-to-many is possible but needs careful handling, since it can produce ambiguous or duplicated results if you're not paying attention.
Power BI allows only one active relationship between two tables at a time, and that's the one used automatically in calculations. Any other relationship between the same two tables has to be marked inactive, and you activate it inside a specific DAX measure using the USERELATIONSHIP function when you need that alternate path instead.
A common case is having both an OrderDate and a ShipDate on the same fact table. You'd relate both to the Date table, but only one can be active by default, usually OrderDate. Any measure that needs to calculate against ShipDate instead has to explicitly invoke that inactive relationship.
A snowflake schema breaks dimension tables down further into sub-dimensions, so a Product dimension might split into separate Product and Category tables instead of one flat table. It saves some storage space through normalization, but adds extra joins that can slow queries and complicate DAX. Most Power BI models default to a star schema and only snowflake where there's a real reason to.
DAX, short for Data Analysis Expressions, is the formula language behind Power BI's calculations. You use it to build measures, calculated columns, and calculated tables. If you've used Excel formulas before, DAX will look familiar on the surface, but it behaves very differently once you get past basic aggregation.
A calculated column is computed row by row when data refreshes and gets physically stored in the table, taking up memory whether you use it or not. A measure is computed on the fly, based on whatever filters are currently applied in the visual, and isn't stored anywhere. Measures are almost always the better choice for aggregations like totals and ratios.
Total Sales = SUM(Sales[Amount]). That's it for the simple case. Real-world measures usually get wrapped in something more, but every complex DAX formula starts from an aggregation this basic.
SUM adds up values from a single column directly. SUMX is an iterator, it goes row by row through a table, evaluates an expression for each row, then sums the results. You need SUMX when the calculation itself has to happen per row, like multiplying quantity by unit price before summing, since that multiplication can't happen on a single pre-existing column.
COUNT counts numeric values in a column and ignores blanks. COUNTA counts non-blank values regardless of type, numbers, text, dates, all count. COUNTROWS counts the total rows in a table, ignoring column content entirely. Picking the wrong one is a common source of an off-by-a-few-thousand bug in a report nobody notices until finance asks why the numbers don't match.
IF returns one value when a condition is true and another when it's false, same idea as Excel. For example, Status = IF(Sales[Amount] > 1000, "High Value", "Standard") tags each row based on its amount. For more than two branches, SWITCH is usually cleaner than nesting several IFs inside each other.
Power Query is the data preparation layer. It runs before anything hits the data model, handling connecting to sources, cleaning messy columns, merging tables, and reshaping data. If DAX is where you calculate things, Power Query is where you make sure the raw data is actually usable in the first place.
M, sometimes called the Power Query Formula Language. Most people never write M directly, since the Power Query Editor's point-and-click interface generates it automatically behind the scenes, but you can open the Advanced Editor and see (or hand-write) the actual M code any time.
They're really the same thing described two ways. Every transformation you perform, removing a column, filtering rows, changing a data type, becomes a recorded step in the Applied Steps list on the right side of the editor. Power Query replays those steps in order every time the data refreshes.
Select the column or columns that define a duplicate, then use Remove Duplicates from the Home tab. Power Query keeps the first occurrence it finds and drops the rest. It's worth checking which columns you select carefully, since removing duplicates based on the wrong column can quietly delete rows you actually needed.
Merge Queries joins two tables together based on a matching column, similar to a SQL join. You pick a join type, inner, left outer, right outer, full outer, and Power Query combines the tables accordingly. It's how you'd pull a customer's region into a sales table when the two live in separate source tables.
Merge joins tables side by side based on a matching key, adding columns. Append stacks tables on top of each other, adding rows, which only makes sense when the tables share the same or similar column structure, like combining twelve months of separate sales files into one table.
A slicer is a visual element sitting directly on the report canvas that users can click to filter what they see, and it's always visible. A filter lives in the Filters pane, can be applied at the visual, page, or report level, and doesn't take up canvas space the way a slicer does. Slicers are for interactive, visible controls. Filters are more for behind-the-scenes constraints.
Drill-down lets a user click into a summary value to see the next level of detail underneath it, like clicking a Year total to see the Quarters inside it. You set it up by adding a hierarchy of fields (Year, Quarter, Month) to a visual's axis, and Power BI automatically enables the drill controls on that visual.
Purely orientation. A column chart shows bars running vertically, a bar chart shows them running horizontally. Which one to use usually comes down to label length. Long category names read better on a horizontal bar chart, since there's more room for the text.
Conditional formatting changes how a visual looks based on the underlying data, like coloring a table cell red when a value falls below target. It's available on tables, matrices, and some chart types, and can be driven by a fixed rule, a color scale, or even the value of a separate measure.
A bookmark captures the current state of a report page, which filters are applied, which visuals are visible, even the current drill level, and lets you return to that exact state later with one click. They're commonly used to build a story-like navigation flow or to create toggle buttons that switch between different views of the same data.
File, then Publish, then choose the destination workspace. Once it uploads, the report and its underlying dataset both appear in that workspace inside the Power BI Service, ready to be viewed, shared, or added to a dashboard.
Sharing a report gives specific people or groups access, and they need a Power BI account (with the right license) to view it. Publish to Web generates a public, embeddable link that anyone with the URL can open, no login required, which also means it should never be used for anything containing sensitive data.
A dataset is the underlying data model, tables, relationships, and DAX measures, that a report is built on top of. One dataset can actually power several different reports, which is useful when different teams want their own visual layout over the same underlying numbers without duplicating the whole model each time.
Scheduled refresh applies to Import mode, where data is copied into Power BI's own storage and periodically reloaded from the source on a schedule you set, up to 48 times a day on most licenses. DirectQuery skips that copy entirely and queries the source live every time a user interacts with the report, so there's no refresh schedule to configure since the data is always current.
Free, Pro, and Premium (or Premium Per User) are the main tiers. Free lets you build and view your own reports but can't share them with others. Pro adds sharing and collaboration. Premium adds larger capacity, more frequent refreshes, and features like paginated reports, and it's usually purchased at the organization level rather than per person.
A paginated report is built for pixel-perfect, print-ready output, an invoice or a regulatory form, where every element needs to land in a fixed spot on the page. A standard Power BI report is built for interactive, on-screen exploration instead and doesn't paginate cleanly for printing. Paginated reports use a separate tool, Report Builder, rather than Power BI Desktop.
Mid-Level (3-6 years)
Row context exists when a formula evaluates one row at a time, which is what happens naturally inside a calculated column. Filter context is the set of filters currently in effect on a calculation, coming from slicers, visual-level filters, or explicit DAX functions like CALCULATE. A measure's result can change completely depending on filter context alone, which is exactly what makes measures powerful and, early on, confusing.
CALCULATE evaluates an expression inside a modified filter context. You can add filters, remove them, or override them entirely. Something like Total Sales (This Year) = CALCULATE([Total Sales], Date[Year] = 2026) forces that measure to always look at 2026 regardless of what year is actually selected in the report. Nearly every non-trivial DAX pattern, year-over-year comparisons, running totals, percent of total, is built on CALCULATE underneath.
Context transition is what happens when CALCULATE converts row context into an equivalent filter context. It typically shows up inside iterator functions like SUMX, where you're evaluating a measure row by row. Without understanding context transition, a measure that references another measure inside a loop can silently return the wrong numbers, and it's one of the more common places intermediate DAX writers get stuck.
ALL removes every filter on a table or column entirely, ignoring whatever the user has selected. ALLEXCEPT removes all filters except the ones you explicitly list. ALLSELECTED is the trickiest of the three: it keeps filters coming from outside the current visual (like a slicer on the page) but removes filters applied inside the visual itself, which is what makes percent-of-total-within-a-chart calculations work correctly.
YoY Growth = DIVIDE([Total Sales] - CALCULATE([Total Sales], SAMEPERIODLASTYEAR('Date'[Date])), CALCULATE([Total Sales], SAMEPERIODLASTYEAR('Date'[Date]))). SAMEPERIODLASTYEAR shifts the filter context back exactly one year, and DIVIDE handles the division safely, returning blank instead of an error when the denominator is zero.
DIVIDE returns a blank (or a value you specify) when the denominator is zero, instead of throwing a divide-by-zero error that breaks the visual. It also handles blank values in a way the raw operator just doesn't. Using DIVIDE by default is one of those small habits that saves you from a report crashing in front of a client over a single empty row.
The cleanest approach is a bridge table sitting between the two tables that need the many-to-many connection, splitting it into two one-to-many relationships instead. Power BI does support direct many-to-many relationships natively, but they can produce ambiguous filtering behavior and are usually harder to reason about and debug than a bridge table.
A role-playing dimension is one table that logically applies in multiple ways, the classic example being a single Date table that needs to relate to both an OrderDate and a ShipDate on a fact table. Since only one relationship can be active at a time, you'd typically create a second Date table (or use USERELATIONSHIP in specific measures) to handle the second role without ambiguity.
A physical relationship is one you've defined in the model itself, visible in the relationship diagram. A virtual relationship is created on the fly inside a DAX formula, using functions like TREATAS to apply filtering logic between tables that aren't actually related in the model at all. It's a more advanced technique, useful when a real relationship would create ambiguity or performance problems.
It depends on grain and how they're queried. If both tables share the same level of detail and are almost always analyzed together, combining them can simplify the model. If they have different grains, say, one is daily transactions and the other is monthly budgets, forcing them together usually creates more DAX complexity than it saves, and they're better left as separate fact tables connected through shared dimensions.
A composite key combines two or more columns to create a unique identifier when no single column does the job on its own, common when relating tables on something like Year plus Month instead of a single date. Power BI doesn't support native composite key relationships, so you'd typically concatenate the columns into a single merged key column in Power Query first, then relate on that.
Performance Analyzer, built into Power BI Desktop. It breaks down exactly how long each visual took to render and how much of that time went to the actual DAX query versus rendering. That's the starting point before guessing at what's slow, since it's easy to assume a visual is the bottleneck when the real cost is somewhere else entirely.
Remove columns you're not actually using in any visual or measure, since every unused column still gets compressed and stored. Reduce cardinality where possible, splitting a single high-precision datetime column into separate date and time columns often compresses far better than one column with thousands of unique values. Disabling auto date/time tables, which Power BI creates by default per date column, also cuts a surprising amount of hidden bloat in bigger models.
Aggregations are pre-summarized tables sitting behind a detailed fact table, letting Power BI answer a high-level query from a much smaller, faster table instead of scanning millions of detail rows every time. A dashboard tile showing sales by year can be served from a tiny aggregated table, while a detailed drill-through still reaches the full-grain data when someone actually needs it.
A calculated column is computed for every row and physically stored, adding to the model's memory footprint whether it's actually used or not. A measure computes only when it's needed, using whatever filter context is active at that moment, and stores nothing extra at all. At small data volumes the difference is invisible. At millions of rows, it isn't.
Query folding is when Power Query pushes your transformation steps back to the data source itself, so filtering or aggregation happens inside the database rather than after all the raw data has already been pulled into Power BI. It matters enormously for large sources, since folded queries can be dramatically faster and lighter than pulling everything locally first and filtering afterward.
Right-click a step in the Applied Steps list and look at whether "View Native Query" is available. If it is, that step is folding back to the source. Once you hit a step that breaks folding, like a custom column referencing another query, everything after it typically has to run locally instead.
A parameter is a named, reusable value you can reference across multiple queries, instead of hardcoding the same value in several places. A common use is a date-range parameter for a development environment, so you can point every query at a smaller test dataset while building, then swap the parameter value once to point everything at production.
A staging query is disabled from loading into the report (its load is turned off) and exists purely as an intermediate step other queries reference, keeping the raw or partially-transformed data out of the final model entirely. This keeps the model clean and avoids duplicating large amounts of data that nobody needs to see directly in a visual.
RLS restricts what rows of data a user can see based on who they are. You define roles in Power BI Desktop, each with a DAX filter expression, like [Region] = "West", then map users or Azure AD groups to those roles once the report is published to the Service.
Static RLS hardcodes the filter value directly into the role, so "West Region" is a fixed role someone gets assigned to. Dynamic RLS uses a function like USERPRINCIPALNAME() inside the DAX filter to automatically determine which rows a logged-in user should see, based on data in a table that maps users to what they're allowed to view. Dynamic RLS scales far better once you have more than a handful of regions or roles to manage.
Power BI Desktop has a "View As" feature under the Modeling tab that lets you preview the report exactly as a specific role would see it, without needing to actually publish and log in as a different user. It's worth testing every role this way before publishing, since an RLS mistake means someone sees data they shouldn't.
Yes, RLS applies at query time in both modes, but with DirectQuery the filter gets pushed down into the actual SQL query sent to the source database, which adds a small amount of overhead to every single query rather than being applied once during a scheduled refresh, as it effectively is with Import mode.
Senior (6-8 years)
A variable, declared with VAR and used with RETURN, stores the result of an expression once so you can reference it multiple times in the same measure without recalculating it. Beyond the performance benefit of not repeating an expensive calculation, variables make complex measures dramatically easier to read and debug, since each step gets a name instead of being buried inside nested parentheses.
Non-iterator functions like SUM operate directly on a column of already-existing values. Iterators evaluate a full expression row by row across a table before aggregating the result, which is what lets you calculate something like quantity times price, a value that doesn't exist as its own column, and then sum that computed result.
EARLIER lets you reference the row context from an outer loop when you're nested inside another row-context evaluation, but it's confusing to read and easy to get backwards. A variable declared before entering the nested calculation captures the same outer-row value explicitly and by name, which is why most DAX written today uses variables instead of EARLIER, even though EARLIER still works and shows up in older code.
Running Total = CALCULATE([Total Sales], FILTER(ALLSELECTED('Date'), 'Date'[Date] <= MAX('Date'[Date]))). This keeps every date up to and including the current one in the filter context, using ALLSELECTED so it respects whatever slicer selections are active on the report rather than ignoring them entirely.
By default, a filter argument inside CALCULATE replaces any existing filter on that same column. KEEPFILTERS changes that behavior so the new filter is combined with, rather than replacing, the existing one. It matters when you want to narrow a selection further instead of overriding it outright, and getting this wrong is a common cause of a measure returning a number that looks plausible but is quietly counting the wrong rows.
3-Month Avg = AVERAGEX(DATESINPERIOD('Date'[Date], MAX('Date'[Date]), -3, MONTH), [Total Sales]). DATESINPERIOD generates the trailing three-month date window relative to whatever date is currently in context, and AVERAGEX evaluates the Total Sales measure across each of those months before averaging them.
A physical table lives in the model, visible in the fields list, loaded from Power Query. A virtual table exists only transiently, inside a single DAX calculation, produced by functions like FILTER, SUMMARIZE, or VALUES, and disappears once that calculation finishes. Virtual tables let you build intermediate logic without cluttering the model with extra physical tables nobody else needs to see.
A composite model combines multiple storage modes, Import, DirectQuery, and Dual, inside a single data model. It lets you Import your smaller, slower-changing dimension tables for speed while keeping a massive fact table on DirectQuery so it stays current without a full daily reload.
Dual mode lets a table behave as either Import or DirectQuery depending on the context of the specific query being run. It solves a real friction point in composite models: without Dual mode, joining an Imported dimension table to a DirectQuery fact table can force expensive, slow cross-source queries, and Dual mode lets Power BI pick whichever storage mode is faster for that specific query.
Import mode loads data into Power BI's in-memory engine, which is extremely fast to query but requires scheduled refreshes and has model size limits depending on your license. DirectQuery sends a live query to the source on every single interaction, so the data is always current, but performance now depends entirely on how fast the source database itself can respond, which is often the real bottleneck people don't anticipate.
A chasm trap happens when two fact tables share a common dimension but the relationships create ambiguity or duplicate rows when queried together, often surfacing in composite models where different fact tables live in different storage modes and relate to a shared dimension in ways that weren't fully thought through. It typically needs a redesign, like introducing a proper bridge table, rather than a quick DAX patch.
A fan trap happens when one dimension relates to two separate fact tables through a one-to-many relationship on each side, causing values from one fact table to get double-counted when aggregated alongside the other. A chasm trap produces missing or ambiguous rows, while a fan trap produces inflated ones. Both usually get fixed the same way, by restructuring the relationships through a proper bridge table instead of relating the fact tables directly to the shared dimension in a way that lets the duplication happen.
Small, slowly-changing dimension tables are strong Dual or Import candidates, since the storage cost is tiny and the performance win is real. Massive, frequently-updated fact tables usually stay DirectQuery, since importing them fully would be too large or too stale between refreshes. I'd rather start with a mostly-Import model and move only the tables that genuinely need real-time freshness to DirectQuery, instead of defaulting the whole model to DirectQuery and fighting performance everywhere.
Lead (8-10 years)
Deployment pipelines let you promote content through Development, Test, and Production workspace stages in a controlled way, rather than manually republishing files and hoping nothing breaks. Each stage can point to different data sources through parameters, so Test can safely point at a staging database while Production points at the real one.
A dataflow runs Power Query transformations independently of any specific report, storing the cleaned output centrally so multiple reports and datasets can reuse it without each one re-implementing the same logic. It's the difference between one team's private cleanup script and a shared, governed source of truth other teams can build on top of.
Keep the model itself lean and well-documented: clear table and column names, sensible display folders, useful descriptions on measures, and a stable set of core measures that cover the common analytical needs. I'd version it deliberately and communicate changes to downstream report builders before making them, since a reused model's mistakes and improvements both ripple across every report built on it.
Premium capacity provides dedicated compute and memory rather than shared resources, enabling larger model sizes, more frequent refreshes, paginated reports, and features like deployment pipelines and dataflows at scale. It also removes the need for every individual viewer to hold a Pro license, since content on Premium capacity can be viewed with just a free license.
For anything beyond a single team's use, I'd run a gateway cluster rather than a single gateway machine, so there's failover if one node goes down and load gets distributed across more than one server. I'd also keep the gateway's service account permissions scoped tightly to only the data sources it actually needs, rather than a broad admin account that's convenient but risky.
A shared dataset is the finished model, relationships and DAX measures included, ready for a report to be built directly on top of it. A shared dataflow is an earlier stage, cleaned and shaped data without a full semantic model on top yet, meant to feed into multiple different datasets rather than being consumed directly by a report.
I'd start with a clear workspace naming and ownership convention, so it's obvious at a glance who owns what. Certified and promoted datasets give report builders a trusted, sanctioned starting point instead of everyone rebuilding the same model from scratch with slightly different logic. Regular audits of who has access to what, using the admin portal's usage metrics, catch access sprawl before it becomes a real problem.
A promoted dataset is self-declared by its owner as good for reuse, with no formal review behind that claim. A certified dataset has gone through an organization's actual governance review process, typically by a designated admin or data team, and shows a distinct badge signaling that other report builders can trust its quality and structure.
The Power BI admin portal's usage metrics report gives visibility into which reports and dashboards are actually being viewed, by whom, and how often. I'd combine that with the activity log (accessible via the admin API) for a more granular audit trail of specific actions, useful for security reviews and for finding out which reports have quietly gone unused and are safe to retire.
Sensitivity labels, integrated with Microsoft Purview Information Protection, classify content by confidentiality level, like Public, Internal, or Confidential, and can enforce protections like restricting export or sharing based on that label. They matter because they let a security classification follow the data even after it leaves Power BI, say, once it's exported to Excel or PDF.
I'd push for a middle ground: give the business team self-service report-building freedom, but restrict them to building on top of certified, IT-governed datasets rather than connecting directly to raw source systems themselves. That protects data quality and consistency at the model layer while still letting the business team move fast on the actual reporting and visuals, which is usually what they cared about in the first place.
Admin can manage workspace access and settings. Member can publish and edit content plus manage some access. Contributor can publish and edit but can't manage workspace-level access. Viewer can only view. In practice, I'd keep Admin to a small, deliberate group, give report developers Contributor rather than Member by default, and reserve Member for people who genuinely need to manage who else gets in.
Start from actual current usage, model sizes, refresh frequency and duration, concurrent user counts, rather than a rough guess based on headcount alone. I'd load-test against a realistic mix of reports before committing to a capacity SKU, and build in headroom for the specific things that tend to grow faster than expected, like dataset size and the number of scheduled refreshes running in parallel.
Staff (10+ years)
It comes down to how much teams actually share definitions and metrics versus how much their needs genuinely diverge. A single shared model works well when core metrics like revenue or headcount need one consistent definition across the company. I'd still let teams extend that shared model with their own composite layers on top for department-specific needs, rather than forcing every possible metric into one monolithic model that becomes impossible to change safely.
Migrate incrementally, starting with the reports that have the most reach and the most manual, error-prone effort behind them, since that's where the win is most visible and most likely to build buy-in. I'd run the Power BI version and the legacy Excel version in parallel for at least one reporting cycle before fully cutting over, so any discrepancy in the numbers gets caught and explained before anyone stops trusting the new tool.
I look at whether the schema is genuinely star-shaped or secretly snowflaked in a way that will cause performance problems later, whether the grain of each fact table is clearly defined and consistent, and whether measure names and business logic match how the business actually talks about the metric. Catching a modeling mistake before a dozen reports get built on top of it is far cheaper than fixing it after the fact.
I'd document the handful of patterns that cause the most pain when done inconsistently, measure naming conventions, when to use variables, how time intelligence should be handled, rather than a long style guide nobody reads end to end. Pairing on a few real reports early tends to spread those conventions faster than any written document does on its own.
I'd weigh the actual cost of the duplication, inconsistent metric definitions causing arguments in meetings, wasted refresh compute, multiple people maintaining nearly identical logic, against the real disruption of a migration that touches reports people already rely on daily. Consolidation is usually worth it once the same core metric has more than two or three independently-maintained definitions floating around the organization.
This is a judgment question interviewers use to see how you reason under uncertainty, not to test a specific fact. A strong answer names the actual constraint that forced the decision, the realistic options that were genuinely on the table, why you picked one knowing it wasn't guaranteed to be right, and what you'd do differently with what you know now.
First check whether it's actually the same report and the same filters, since a slow user is often applying a different slicer combination that happens to hit a much larger portion of the data. Beyond that, I'd look at whether RLS is involved, since row-level security filters add real query overhead that can vary a lot depending on how much data a given user's role actually has access to.
The admin portal and the Power BI REST API both expose refresh history, and I'd wire failure events from there into whatever alerting channel the team already watches, rather than relying on someone noticing a stale report days later. For anything business-critical, I'd also track refresh duration over time, beyond simple success or failure, since a refresh that's gradually getting slower is an early warning sign before it eventually times out and actually fails.
Treat the model's measures and column names as a contract with everyone who's built a report on top of it. Additive changes, new measures, new columns, are generally safe. Renaming or removing something existing needs a heads-up and a transition period, since a silent rename can quietly break report visuals with no obvious error message pointing back to the actual cause.
Mitigate first: flag the dashboard as under investigation so nobody makes a decision off numbers that might be wrong, before digging into root cause. Then I'd trace it systematically, checking filter context and any active RLS role first, since a mismatched filter or an unexpected security filter causes far more of these discrepancies in practice than an actual DAX logic bug does.
Look at historical usage patterns from the last comparable event if one exists, and load-test against a realistic mix of the specific reports people will actually be hammering, beyond general traffic volume alone. I'd also make sure refresh schedules for the busiest datasets are timed to finish well before that peak usage window starts, so people aren't hitting a dataset that's mid-refresh and returning stale or locked results.
I'd pair on one of their actual measures, run it through Performance Analyzer together, and show them concretely where the time is going rather than just telling them their DAX is inefficient in the abstract. Seeing their own measure take four seconds and understanding exactly why sticks far better than a generic lecture on calculated columns versus measures ever does.
I wouldn't lead with the technology. I'd find the specific pain they already feel, a report that takes someone three days to reconcile every month, or numbers that occasionally disagree between two people's spreadsheets, and show a working prototype solving exactly that, rather than pitching Power BI as a platform in the abstract. People change tools when the new one visibly saves them a specific, named headache.
I'd bring the actual downstream cost of their proposed approach, slower report performance, harder DAX, more duplicated logic across reports, backed by a concrete example rather than a general preference for star schemas. Most disagreements like this resolve once both sides are looking at the same real trade-off instead of arguing from first principles past each other.
I'd translate the technical benefits into numbers leadership already tracks: hours saved per week from removing manual Excel consolidation, the cost of a specific outage or slow-report incident that's happened before, or growth headroom for a dataset that's about to outgrow shared capacity. A capacity upgrade framed as risk reduction and time saved lands better than one framed as a feature list.




