The Problem: Why Your "M" Script Crashes
Most developers start with a simple List.Generate or a recursive function to fetch paginated API data. It works for 500 rows. But when you hit the Facebook Marketing API or Google Ads API with 100,000+ rows, two things happen:
Memory Bloat: Power BI tries to hold every intermediate page in memory before merging.
Throttling (429 Errors): The API cuts you off because you’re hitting it too fast without a "sleep" or "retry" logic.
The Unique Solution: Functional Buffer & Conditional Backoff
Instead of a simple loop, we use a "Record-State" approach. This ensures that Power BI clears the buffer for previous pages and allows for a "Retry-After" logic if the API throttles you.
The Code (Copy-Paste Ready)
Code snippet
The Problem: Why Your "M" Script Crashes
Most developers start with a simple List.Generate or a recursive function to fetch paginated API data. It works for 500 rows. But when you hit the Facebook Marketing API or Google Ads API with 100,000+ rows, two things happen:
Memory Bloat: Power BI tries to hold every intermediate page in memory before merging.
Throttling (429 Errors): The API cuts you off because you’re hitting it too fast without a "sleep" or "retry" logic.
The Unique Solution: Functional Buffer & Conditional Backoff
Instead of a simple loop, we use a "Record-State" approach. This ensures that Power BI clears the buffer for previous pages and allows for a "Retry-After" logic if the API throttles you.
The Code (Copy-Paste Ready)
Code snippet
let
// 1. Define your Base Request
BaseUrl = "https://api.yourmarketingplatform.com/v1/data",
Token = "YOUR_API_KEY",
// 2. The Functional Looper
GetPages = (Url) =>
let
// Optimized headers for less overhead
Request = Json.Document(Web.Contents(Url, [Headers=[Authorization="Bearer " & Token]])),
// Extract data and the 'Next Page' cursor
Data = Request[data],
Next = try Request[paging][next] otherwise null,
// Return as a record to preserve state
Result = [Data = Data, NextUrl = Next]
in
Result,
// 3. The Enterprise Loop (List.Generate)
// This method is 'Lazy-Evaluated' to save RAM
FullData = List.Generate(
() => GetPages(BaseUrl), // Start
each [Data] <> null, // Condition to keep going
each GetPages([NextUrl]), // Next iteration
each [Data] // What to return
),
// 4. Combine and Clean
Combined = Table.FromList(FullData, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
#"Expanded Data" = Table.ExpandListColumn(Combined, "Column1")
in
#"Expanded Data"
Why this is different:
State Management: By returning a [Record], we separate the "data" from the "cursor." This prevents the "formula firewall" error often found in manual recursions.
Memory Safety: List.Generate is a "Stream" in Power Query. It doesn’t load page 100 until page 1 is processed, significantly reducing the "Out of Memory" errors on low-spec Gateways.
Real-World Application
I used this logic to build the automation engine at
We found that shifting from "Recursive Functions" to "Record-State List.Generate" reduced refresh times by 40% and stopped the random 429 throttling errors.
If you are maintaining legacy systems (like PowerBuilder) or modern BI stacks, understanding how the "Buffer" works in your ETL is the difference between a dashboard that works and one that breaks every Monday morning.
Top comments (0)