MICROSOFT POWER BI • DATA PREPARATION WITH POWER QUERY

Applied Steps & Query Folding — Understand applied steps and query folding conceptually

How Power Query translates your transformations into native data-source queries for maximum performance.

Historical Context & Motivation

The challenge of efficiently transforming data before analysis has been a central concern in data engineering for decades. In the early days of business intelligence, ETL pipelines were hand-coded in SQL or proprietary scripting languages, requiring developers to manage every detail of extraction, transformation, and loading. Microsoft recognized that this workflow was a bottleneck—especially for analysts who were not SQL experts—and began embedding data-shaping tools directly into end-user products. The evolution from rudimentary import wizards in Excel to the fully featured Power Query engine represents a paradigm shift: a functional, lazy-evaluation query language accessible through a graphical interface, with the critical optimization of query folding operating behind the scenes.

2010
Power Pivot & Early Data Shaping
Microsoft releases Power Pivot as an Excel add-in, introducing the xVelocity (VertiPaq) in-memory engine. Data import is possible, but transformation capabilities are minimal—users rely on SQL views or SSIS packages for pre-processing.
2013
Power Query Add-in for Excel
Originally codenamed 'Data Explorer,' Power Query ships as a free Excel add-in, introducing the M language (informally, the 'Mashup' language) and the concept of recorded applied steps. Query folding debuts as an internal optimization for supported connectors.
2015
Power BI Desktop Launches
Microsoft bundles Power Query into Power BI Desktop as the primary data-ingestion layer. The Applied Steps pane becomes the canonical interface for inspecting and editing transformations, making the step-based pipeline visible to every Power BI user.
2018–2020
Get & Transform in Excel, Dataflows
Power Query becomes 'Get & Transform' natively inside Excel. Microsoft also introduces Power BI Dataflows, enabling centralized, cloud-hosted Power Query logic. Query folding behavior is extended and documented more formally for enterprise use.
2023–Present
Fabric & Universal Query Folding Indicators
Microsoft Fabric integrates Power Query across data engineering, data science, and BI workloads. The Power Query editor introduces step-level folding indicators—green, yellow, and dashed icons—giving users explicit feedback on which steps fold to the source.

The fundamental question this lesson addresses is deceptively simple: when you click buttons in the Power Query editor, what exactly happens under the hood, and why does it matter whether those transformations execute on the data source or in the local engine? Understanding the interplay between applied steps and query folding is essential for building Power BI solutions that scale from thousands to millions of rows without degrading refresh times or overwhelming local memory.

Core Principles & Definitions

Power Query operates on a principle that will feel familiar to any computer science student who has studied functional programming or compiler optimization: transformations are specified declaratively as a chain of immutable expressions, and an evaluation engine decides how best to execute them. Every interaction in the graphical editor—filtering a column, changing a data type, merging two tables—generates a corresponding expression in the M language (formally, the Power Query Formula Language). These expressions are organized sequentially as applied steps, and each step references the output of the previous one, forming a directed acyclic pipeline analogous to a UNIX pipe chain or a Spark DAG.

1

Applied Steps

An ordered sequence of M expressions displayed in the Query Settings pane. Each step is a named let...in binding that transforms the output of its predecessor. Steps are lazily evaluated—only the final result (or a preview sample) is materialized until refresh time.
2

Query Folding

The process by which the Power Query engine translates one or more M steps into a single native query (e.g., SQL, OData filter expression) that executes on the remote data source. It is conceptually identical to predicate pushdown in distributed query engines like Apache Spark or Presto.
3

The M Language

A functional, case-sensitive, expression-based language. Every query is a single let expression with named steps and an in clause that returns the final table. Because M is purely declarative, the engine has freedom to reorder or fold operations.
4

Foldable vs. Non-Foldable Operations

Operations like filtering rows, selecting/removing columns, sorting, grouping, and renaming columns are typically foldable to SQL. Custom column formulas using M-specific functions, pivoting, or merging tables from different sources usually break folding.
5

Folding Indicators

Modern Power Query editors annotate each step with a folding indicator: a solid green icon means the step folds, a dashed icon means it does not, and a yellow icon indicates partial folding. These indicators make query optimization transparent without requiring manual View Native Query inspection.
KEY TAKEAWAY
Think of query folding like a compiler optimization pass. Just as a compiler can fuse multiple loop operations into a single pass over an array, the Power Query engine fuses multiple transformation steps into a single SQL statement. When folding succeeds, the database's optimized execution engine does the heavy lifting—filtering billions of rows with indexed scans—instead of pulling all the data across the network for local processing. A broken fold is analogous to a compiler bailout: the remaining work falls on the slower, less-optimized runtime.

Visual Explanation — The Applied Steps Pipeline

The diagram above illustrates a seven-step query. Steps 1 through 4 reside in the folded zone and are compiled into a single native SQL statement sent to the data source. At Step 5, the introduction of a custom M function that has no SQL equivalent creates a fold boundary. All subsequent steps execute in the local Power Query engine, processing only the rows returned by the folded SQL. The position of this boundary directly determines the volume of data transferred over the network.

Notice the structural analogy to query planning in relational databases: the Power Query engine acts as a query optimizer that pushes as many operations as possible to the data source, just as a database optimizer pushes predicates below join nodes in an execution plan. The green-bordered steps are conceptually equivalent to operators that can be translated to the native query dialect, while the red-bordered steps represent operators that must remain in the local execution context. As a computer science student, you can think of the fold boundary as the point at which the abstract syntax tree of your M expression can no longer be transpiled into the target language.

How Query Folding Works — The Engine Internals

Query folding is not a single algorithm but rather a family of source-specific translators embedded within each Power Query connector. When you connect to a SQL Server, the connector knows how to map a subset of M functions to T-SQL clauses. For an OData feed, the mapping targets OData query parameters like $filter and $select. The engine processes the M expression tree from the source step outward, greedily folding each subsequent step until it encounters an operation that the connector cannot translate. This greedy, prefix-based folding strategy has important implications for step ordering.

The Folding Decision Process

Internally, the engine maintains a representation of the cumulative folded query as it walks the step chain. At each step i, it attempts to extend the native query by appending the equivalent clause. If the attempt succeeds, the folded query is updated and the engine proceeds to step i + 1. If it fails, the engine materializes the result of the folded query up to step i − 1 and evaluates all remaining steps locally. Crucially, once folding breaks, it cannot resume for subsequent steps—even if those later steps would individually be foldable. This is the 'broken chain' property.

FOLDING CHAIN PROPERTY
Folded(Q) = Fold(s₁) ∘ Fold(s₂) ∘ … ∘ Fold(sₖ) where k = max prefix of foldable steps
Here, sᵢ represents the i-th applied step, Fold() is the connector-specific translation function, and denotes composition. Steps sₖ₊₁ … sₙ execute locally in the mashup engine.
DATA TRANSFER VOLUME
D_transferred = |Rows(Fold(s₁ … sₖ))| × |Cols(Fold(s₁ … sₖ))| × avg_cell_size
The volume of data pulled from the source is determined entirely by the result set of the folded query. Maximizing k (the number of folded steps) minimizes D. If k = 0 (no folding), the entire source table is transferred.
Step Ordering Matters
Because folding is prefix-based and non-resumable, placing a non-foldable step early in the pipeline breaks the chain for all subsequent steps. A common optimization strategy is to reorder steps so that all foldable operations appear first and non-foldable operations are deferred to the end. This is analogous to pushing selections below projections and joins in relational algebra—a principle you may recognize from database systems coursework.

Classifying Operations — Foldable vs. Non-Foldable

Not all Power Query transformations are created equal from a folding perspective. Whether an operation folds depends on two factors: the capability of the connector and the expressiveness of the target query language. A SQL Server connector can fold a wide range of operations because T-SQL is richly expressive, whereas a flat-file CSV connector supports no folding at all since there is no query engine on the other end. The following classification provides a general guide, though specific behavior can vary by connector version and data source.

This three-column classification shows common Power Query operations grouped by their foldability to SQL-based sources. Always-foldable operations map directly to SQL clauses. Sometimes-foldable operations depend on context—merges only fold when both tables share the same source connection. Never-foldable operations use M-specific constructs with no SQL equivalent, forcing local evaluation.

A useful mental model from compiler theory: foldable operations are the subset of the M language that falls within the intersection grammar of M and SQL—operations expressible in both languages. When you use an M-only construct (like iterating over a list within each row using List.Transform), you step outside that intersection, and the translator can no longer produce equivalent SQL. The practical consequence is clear: to maximize folding, you should express your transformations using the simplest, most standard operations possible, deferring complex M-specific logic to the end of the step chain.

Worked Example — Optimizing a Query for Folding

Consider a scenario where you connect to a SQL Server database containing a Sales table with 50 million rows. You need to produce a summary showing total revenue by product category for the year 2024, with a custom column classifying categories as 'High' or 'Low' revenue. We will build the query in two ways—a naive ordering that breaks folding early, and an optimized ordering that maximizes it—then compare the results.

Scenario: Sales Revenue Summary with Folding Optimization
1
Step 1 — Naive Approach: Inspect the Unoptimized QueryA user connects to the Sales table and records the following applied steps in order: (1) Source, (2) Add Custom Column — classifies each row using if [Revenue] > 10000 then "High" else "Low", (3) Filter Rows — keep only Year = 2024, (4) Remove Columns — drop unnecessary fields, (5) Group By Category — sum revenue. Because Step 2 uses a custom M conditional expression, folding breaks at Step 2. Steps 3–5, which are normally foldable, now execute locally on all 50 million rows.
Folding breaks at Step 2. All 50M rows are downloaded. Refresh time: ~12 minutes.
2
Step 2 — Identify the Fold-Breaking StepRight-click Step 2 (Add Custom Column) and select View Native Query. The option is grayed out, confirming this step does not fold. Right-click Step 1 (Source) and the native query is available—a simple SELECT * FROM Sales. The folding indicator on Step 2 shows a dashed line icon. The key insight: the custom column's M expression cannot be translated to T-SQL by the connector.
Fold boundary identified: between Step 1 and Step 2.
3
Step 3 — Reorder Steps to Maximize FoldingWe restructure the applied steps to place all foldable operations before the non-foldable custom column: (1) Source, (2) Filter Rows — WHERE Year = 2024, (3) Remove Columns — SELECT only needed fields, (4) Group By Category — GROUP BY with SUM, (5) Add Custom Column — classify categories. Now Steps 1–4 form an unbroken foldable chain. The engine generates: SELECT [Category], SUM([Revenue]) AS [TotalRevenue] FROM [Sales] WHERE [Year] = 2024 GROUP BY [Category]. Only the aggregated result (perhaps 20 rows) crosses the network.
Steps 1–4 fold. Only ~20 rows transferred. Refresh time: ~3 seconds.
4
Step 4 — Verify the Optimized FoldRight-click Step 4 (Group By) and select View Native Query. The generated SQL confirms the filter, column selection, and aggregation are all pushed to the server. The folding indicator shows solid green icons on Steps 1–4 and a dashed icon on Step 5 only. The custom column now operates on just 20 rows instead of 50 million.
Performance improvement: from ~12 minutes to ~3 seconds—a 240× speedup achieved purely by reordering steps.
5
Step 5 — Validate the M CodeOpen the Advanced Editor to inspect the final M code. The optimized query reads: let Source = Sql.Database("server", "db"), Sales = Source{[Schema="dbo",Item="Sales"]}[Data], FilteredRows = Table.SelectRows(Sales, each [Year] = 2024), RemovedCols = Table.SelectColumns(FilteredRows, {"Category", "Revenue"}), Grouped = Table.Group(RemovedCols, {"Category"}, {{"TotalRevenue", each List.Sum([Revenue]), type number}}), AddedCustom = Table.AddColumn(Grouped, "Tier", each if [TotalRevenue] > 100000 then "High" else "Low") in AddedCustom. Each let binding corresponds to one applied step, and the order directly controls where folding breaks.
Final optimized M code confirmed with 4 folded steps and 1 local step.

Strengths, Limitations & Tradeoffs of Query Folding

Strengths and limitations of query folding in Power Query
AspectStrengthLimitation
PerformanceDramatically reduces data transfer and leverages source-side indexing, parallelism, and caching. Refresh times can drop from hours to seconds for large datasets.Performance gains depend entirely on the source engine's optimization. A poorly indexed SQL table may not benefit much from folded queries.
TransparencyFolding indicators and View Native Query provide direct feedback. Users can inspect the exact SQL generated, aiding debugging and optimization.Folding is 'all or nothing' per step prefix—partial folding within a single step is not exposed. Users may not realize a step broke the chain without checking indicators.
ExpressivenessM is highly expressive, allowing arbitrary transformations including list operations, web API calls, and custom functions. Users are not limited to what folds.The most expressive M constructs are exactly the ones that do not fold. There is an inherent tension between transformation flexibility and folding efficiency.
Source CompatibilityMany enterprise connectors (SQL Server, Oracle, PostgreSQL, Azure Synapse, Snowflake) support robust folding. OData and some APIs support partial folding.Flat files (CSV, Excel), web scraping, and many REST APIs have no query engine, so folding is impossible. JSON and XML sources have very limited support.
MaintenanceApplied steps are version-controlled via the M code and can be shared through Dataflows or deployment pipelines.Step ordering for optimal folding must be manually maintained. Inserting a step in the wrong position can silently break the fold chain with no error—only degraded performance.
KEY TAKEAWAY
Query folding occupies the same design space as JIT compilation in language runtimes: it attempts to translate high-level user instructions into efficient low-level operations automatically, but the translation is only possible for a subset of the language's features. Just as a JIT compiler falls back to interpretation for unsupported bytecodes, Power Query falls back to local evaluation for non-foldable M operations. The practitioner's job is to keep the 'hot path'—the operations processing the most data—within the foldable subset, just as a performance engineer keeps critical code within JIT-friendly patterns.

Connection to Advanced Theory — Dataflows, Incremental Refresh & Beyond

The concepts of applied steps and query folding are foundational to several advanced Power BI features that rely on the engine's ability to generate targeted native queries. Understanding folding unlocks comprehension of these more sophisticated mechanisms, which are increasingly important in enterprise-scale deployments.

How applied steps and query folding concepts extend to advanced Power BI features
Foundational ConceptAdvanced FeatureRelationship
Applied Steps (sequential M bindings)Power BI DataflowsDataflows externalize applied steps into a shared, cloud-hosted layer. Step chains become reusable ETL artifacts stored in Azure Data Lake, enabling cross-report consistency and centralized data prep.
Query Folding (predicate pushdown)Incremental RefreshIncremental refresh requires query folding to work: Power BI generates date-range predicates (WHERE Date >= @start AND Date < @end) that must fold to the source so only new/changed partitions are refreshed.
Fold Boundary AwarenessComposite Models & DirectQueryIn DirectQuery mode, every DAX query triggers a Power Query evaluation. If steps do not fold, DirectQuery becomes impractical because every user interaction would pull and transform the full dataset locally.
M Language FluencyCustom Connectors & SDKWhen building custom connectors with the Power Query SDK, developers implement the folding logic themselves, defining which M functions map to their API's query parameters. Understanding folding conceptually is prerequisite to implementing it programmatically.

As Microsoft continues to develop Fabric and its unified analytics platform, the query folding paradigm is extending beyond traditional databases. Folding to Spark endpoints, Lakehouse SQL analytics, and even KQL (Kusto Query Language) clusters is becoming increasingly supported. The conceptual framework you have learned here—sequential declarative transformations with greedy prefix-based folding to a native query language—remains the invariant architecture across all these targets. Mastering this mental model now positions you to work effectively with any future connector or compute target that Microsoft introduces.

Practice Problems

PROBLEM 1CONCEPTUAL
Explain why query folding is described as 'prefix-based' and 'non-resumable.' What structural property of the applied steps chain makes this the case, and what is the consequence for a step that is individually foldable but appears after a non-foldable step?
PROBLEM 2BASIC CALCULATION
A query has 8 applied steps. Steps 1–5 fold to SQL, Step 6 is a Pivot operation (non-foldable), and Steps 7–8 are foldable column removals. The source table has 10 million rows and 20 columns. After Step 5's folded query executes, 200,000 rows and 8 columns are returned. Assuming an average cell size of 50 bytes, calculate the data transfer volume in megabytes for both the optimized (current) scenario and the worst case where no steps fold.
PROBLEM 3INTERMEDIATE
You are given a Power Query with the following applied steps: (1) Source — SQL Server, (2) Changed Type, (3) Added Custom Column using Text.Combine(List.Transform({[FirstName], [LastName]}, Text.Upper), " "), (4) Filtered Rows — Status = 'Active', (5) Removed Columns — drop 10 unused columns, (6) Sorted Rows by LastName. Identify the fold boundary, propose an optimized step order, and state which steps would fold after your reordering.
PROBLEM 4APPLIED
You are designing a Power BI solution for a logistics company. The source is an Azure SQL Database with a Shipments table containing 200 million rows. You need to implement incremental refresh that processes only the last 7 days of data on each refresh. The business also requires an unpivoted view of delivery metrics. Explain why query folding is a hard requirement for incremental refresh, describe where you would place the unpivot step in the applied steps chain, and discuss what would happen if the unpivot were placed before the date filter.
PROBLEM 5CRITICAL THINKING
The current query folding architecture uses a greedy, prefix-based algorithm that cannot resume folding after a break. Propose an alternative architecture that could theoretically achieve better folding coverage. Discuss the tradeoffs your alternative introduces in terms of implementation complexity, query correctness guarantees, and performance predictability. Consider how your proposal relates to query optimization techniques in distributed database systems.

Lesson Summary

Applied steps are the ordered sequence of named M language expressions that form a Power Query transformation pipeline. Each step references its predecessor's output, creating a functional, immutable chain that is lazily evaluated at refresh time. Query folding is the optimization process by which the Power Query engine translates a maximal prefix of these steps into a single native query (typically SQL) that executes on the data source, minimizing data transfer and leveraging the source's optimized execution engine.

The folding algorithm is greedy and non-resumable: once a step breaks the fold, all subsequent steps execute locally regardless of their individual foldability. This makes step ordering a primary optimization lever—placing foldable operations (filters, column selections, sorts, groupings) before non-foldable operations (pivots, custom M functions, cross-source merges) maximizes the data reduction performed by the source engine. This concept is foundational to advanced features like incremental refresh, Dataflows, and DirectQuery composite models, all of which depend on efficient fold chains to function at scale.

Varsity Tutors • Microsoft Power BI • Applied Steps & Query Folding