The orienting fact: flows do not get their own governor budget. A flow runs inside the same transaction as everything else on the save, drawing from the same 100 SOQL queries, 150 DML statements, 10,000 DML rows, and 10,000 ms of CPU that Apex draws from. Three flows, two triggers, and a managed package on one Account update share a single allowance. That is the whole model, and almost every flow limit failure follows from it.
The per-transaction numbers you are actually working against
| Limit | Synchronous | Asynchronous |
|---|---|---|
| SOQL queries | 100 | 200 |
| Records retrieved by SOQL | 50,000 | 50,000 |
| DML statements | 150 | 150 |
| Records processed by DML | 10,000 | 10,000 |
| CPU time | 10,000 ms | 60,000 ms |
| Heap size | 6 MB | 12 MB |
| Callouts | 100 | 100 |
These are the Apex per-transaction limits, and they are what your flow spends. Get Records consumes a SOQL query. Create, Update, and Delete Records each consume a DML statement plus as many DML rows as the collection holds. Invocable Apex called from the flow spends from the same pool — it does not get a fresh 100.
One detail on CPU time, because it catches most people: it measures application-server compute only — Apex, flow interpretation, validation rules, managed package code. It excludes database execution for SOQL and DML, and it excludes waiting on a callout. A transaction can take 40 seconds of wall clock and pass, while one that spends 11 seconds looping fails.
The flow-specific limits, and which ones are still real
Several numbers people quote from memory no longer apply.
Executed elements per interview. Salesforce’s General Flow Limits documentation records a maximum of 2,000 flow elements in API version 56.0 and earlier, removed in API version 57.0. That is governed by the API version on the flow version, not the org, so a flow you last edited years ago may still be under the old ceiling. Check the version properties before assuming it is gone.
Paused and waiting interviews. The old cap on simultaneously paused interviews is gone — the release note is titled “Have Unlimited Paused and Waiting Flows.” If you designed around that limit, revisit it.
Schedule-triggered flow interviews. 250,000 per 24 hours, or the number of user licenses in your org multiplied by 200, whichever is greater. This one is real, and a nightly flow with unfiltered entry conditions over a large object will find it.
Loop iterations. There is a cap, and exceeding it produces an error about the number of iterations being exceeded. I will not quote a figure, because it has moved and interacts with the element limit above — check Per-Transaction Flow Limits if you are near it. In practice, a loop running that many iterations has a CPU problem first.
Versions per flow. One version is active at a time, and the total number of saved versions is capped. Delete old versions when a flow gets crowded rather than finding out mid-deployment.
The limit that actually bites is CPU time, and loops are why
Ranked by how often they are the culprit in a real org:
1. CPU time. 10,000 ms
Flow is interpreted. Every element execution carries overhead the equivalent line of Apex does not, and under bulk that multiplies.
The specific pattern: a loop over 200 records with three or four assignments and a decision inside it, on an object that also has two other flows and a trigger. Nothing there is unusual and nothing looks expensive. The cost is the sum of every element execution across every interview in the transaction.
What makes CPU worse than the rest is the diagnosis. A SOQL 101 exception names the query. A CPU timeout names nothing. The culprit is usually spread across three pieces of automation, none individually at fault.
2. SOQL queries. 100
Almost always a Get Records inside a Loop. The flow version of a query in a for-loop, failing at exactly the same place: iteration 100. Get the records once before the loop, or move the work into invocable Apex that can do it in one query. This pattern survives review because Flow Builder’s debug run with one record looks perfectly healthy: one iteration, one query, no warning.
3. DML rows. 10,000
Rarely hit by the flow itself, often hit by what the flow sets off. Updating 2,000 Accounts is 2,000 DML rows, but each may cascade into Contact updates, roll-up recalculation on master-detail parents, and territory reassignment. All counted in one transaction. When a flow that worked at 500 records fails at 3,000, this is usually why.
4. DML statements. 150
An Update Records element inside a loop. Same fix: add to a collection variable inside the loop, do one Update Records after it. The single most valuable habit in flow building.
5. Query rows. 50,000
A Get Records with no filter, or a filter on an unindexed field, against an object with real volume. Add a genuine filter or set “First record only”. Capping how many records the flow stores is not the same as capping rows the query returns.
Flow bulkifies across interviews, but not inside loops
A save of 200 records is not 200 transactions. It is one transaction with 200 interviews, and the platform batches the same element across them. 200 interviews executing one Get Records element produces one query, not 200. Salesforce documents this as flow bulkification, and it is why a well-built record-triggered flow uses a handful of queries regardless of batch size.
Elements inside a Loop do not get this treatment. Iterations of a loop within a single interview are sequential, so a Get Records inside a loop is a query per iteration. That asymmetry is the entire reason “no DML or Get Records inside a loop” is the rule it is, not style, mechanics.
Reading consumption in a debug log
Set a trace flag on the user who will run the transaction, Setup → Debug Logs → New, with a debug level setting Workflow and Apex Code to FINEST. Workflow is the category carrying flow detail. At INFO you see almost nothing useful. Run the operation, open the log, and look in two places.
The end of the log carries a cumulative limits block:
LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 24 out of 100
Number of query rows: 1104 out of 50000
Number of DML statements: 6 out of 150
Number of DML rows: 812 out of 10000
Maximum CPU time: 4812 out of 10000
Maximum heap size: 0 out of 6000000
Read that first, before theorising about anything. It names the limit you are near and the headroom left. A managed package produces a separate block per namespace, which is how you discover most of your CPU is being spent by code you did not write.
The body of the log carries FLOW_ELEMENT_BEGIN and FLOW_ELEMENT_END pairs with timestamps. Diffing them across an expensive element tells you where the CPU went. Tedious, and the only direct evidence available.
Two caveats. Debug logs are truncated when they get large and the oldest lines go first, so a 200-record transaction can lose the part you wanted. Narrow the debug level rather than setting everything to FINEST. And test with 200 records: Flow Builder’s debug run checks logic, not consumption, and will never reveal a loop that queries per iteration because it only runs one.
Frequently asked
Does each flow get its own 100 SOQL queries?
No. The budget belongs to the transaction. Every flow, trigger, validation rule, and managed package in that save shares it.
Does moving to an asynchronous path reset the limits?
It gives you a new transaction with the asynchronous budget: 200 SOQL queries, 60,000 ms CPU, 12 MB heap. What you give up is immediacy: the work happens after the commit, the record is visible before it runs, and a failure does not roll back the save.
Which is cheaper on CPU, a flow or Apex?
Apex, materially, for anything loop-heavy. That is not an argument for writing everything in Apex (a flow an admin can maintain has real value), but if a flow is failing on CPU because it loops over thousands of records, rewriting that loop as an invocable Apex action is the fix, not more flow tuning.
Do before-save flows help with limits?
Yes, and substantially. A before-save flow sets fields on the triggering record without a separate DML statement, so it costs no DML statement and no additional save cycle. Anything that only updates the triggering record belongs there.
Why did my flow fail at 200 records when it passed at 50?
Something in it scales with record count: usually a query or DML inside a loop, or a cascade you are not counting. Compare the cumulative limits block at 50 records and at 200. Linear growth in query count is the tell. And no, per-transaction limits cannot be raised by support.
Reference: Execution Governors and Limits and Debug Log, Salesforce Apex Developer Guide. Flow-specific figures per “General Flow Limits”, “Per-Transaction Flow Limits”, “Flow Bulkification in Transactions”, and “Schedule-Triggered Flow Considerations”, Salesforce Help.
