top of page
Search

Salesforce Governor Limits Are Not a Developer Problem: They're the Platform Quietly Telling You Your Architecture Is Wrong

  • Foundree42 Tech Team
  • 6 days ago
  • 7 min read

“Come with me if you want your architecture to live.” 


There's a moment every Salesforce team hits that doesn't happen during design. It doesn't happen during development. It definitely doesn't happen in a clean sandbox with ten test records and everything behaving nicely. 


It happens later. 


Usually in production. Usually under load. Usually at the worst possible time. Right before a demo, right after a release, or while someone important is watching. 


Something fails. 


The logic is fine. The data is clean. No obvious human error. 


It fails because of a limit. 


CPU time exceeded. Too many SOQL queries. Too many DML statements. 

Without fail, someone says it: "We just need to optimize this." 


No. 


You don't need to optimize it. You need to understand why it broke in the first place. 


Governor limits are not bugs. They're not annoyances. They're not even guardrails in the way most people think about them. 


They're the platform doing its best Terminator 2 impression and calmly telling you: 

“Come with me if you want your architecture to live.” 

 


The Lie We Tell Ourselves About Limits 


Most teams carry around a quiet assumption about governor limits. They treat them like performance tuning issues, something that only matters when the org gets big, the data grows, or the code gets "inefficient." 


That assumption is comforting because it implies control. It suggests that the system is fundamentally sound and just needs refinement. 


That's not what limits are telling you. 


Governor limits are not measuring how fast your code runs. They are measuring how much you're trying to do inside a single transaction. When you hit one, the platform isn't saying your implementation is sloppy. 

It's saying you're asking the system to do more work than it is designed to handle in that moment. 


That's not a tuning problem. That's an architectural one. 

 


What a Transaction Actually Is (And Why It Matters More Than You Think) 


To understand limits, you have to understand transactions. Not conceptually, but mechanically. 


When a record is inserted or updated in Salesforce, the platform opens a transaction. That transaction becomes the execution boundary for everything that follows. Inside it, Salesforce runs all relevant automation tied to that event. 


Before-save record-triggered Flows execute first, modifying field values before the record commits. After-save Flows fire once the record is written. Apex triggers run. Invocable Apex executes when called. Validation rules evaluate. Assignment rules apply. Any synchronous logic connected to that change joins the same execution path. 


All of them run together. All of it shares the same limits: 10,000ms of CPU time, 100 SOQL queries, 150 DML statements. All of it contributes to the same total. 


There is no separate pool for Flow. No isolated budget for Apex. No protected space for your "small change. There is only the transaction. 


That's where the illusion breaks down. When something fails, it's rarely one piece of logic. It's everything working at the same time, in the same place, drawing from the same finite pool. 

 


This Isn't a Flow Problem (And It Never Was) 


This isn't a Flow problem. It isn't an Apex problem either. It’s not a Workflow Rules problem. It isn't a Process Builder problem, though both of those are now deprecated, which means orgs that migrated that legacy logic into Flows without rethinking the architecture simply moved the same structural problem into a newer container. 


You can start to see the platform executes Flow and Apex inside the same transaction.


Limits don't care where the work came from, only how much work is being done. 


Flow didn't create the issue. Apex didn't solve it. 


The issue is unbounded work inside a single execution context. 


If you don't control that, it doesn't matter what tool you use. The outcome is the same. 

 


Why Things Feel Fine… Until They Don't 


This is why so many systems seem stable right up until they aren't. 


Each individual change feels reasonable. A new Flow decision here. A quick query there.


A subflow added to handle a new requirement. A small Apex enhancement to support a new integration. 


None of these decisions are inherently bad. In isolation, they're often correct. 


The reality: They're not running in isolation. 


They're stacking inside the same transaction, quietly accumulating cost. Each additional branch, each loop, each query adds a little more weight. Because Salesforce executes all of it together, the system doesn't degrade linearly. 

It holds. Then it tips. 


You don't notice it in development because your data is small. You don't see it in testing because your scenarios are controlled. 


You see it when production traffic, real data distribution, and full automation coverage all collide at once. 


That's when the platform finally responds. 

 


CPU Time Doesn't Care About Your Intentions 


If there's one limit that consistently exposes architectural issues, it's CPU time. 

CPU time doesn't care how your logic is organized. It doesn't care whether you used Flow or Apex. It doesn't care whether your code is elegant, or your design followed best practices. 


It cares about the total work performed within the transaction. 


Every Flow element executed, every Apex instruction evaluated, every loop iteration processed, every conditional branch explored, all of it contributes to the same 10,000ms ceiling. There is no carve-out for well-intentioned logic. 


That's why CPU failures feel random to teams that aren't thinking architecturally.


Nothing changed in one place. Enough changed everywhere. 


CPU limits don't expose bad code. They expose accumulated complexity. 

 


The Trigger-Centric Model Is the Real Problem 


At the center of most of these issues is a model that feels intuitive but doesn't scale well: the idea that when a record changes, everything related to that change should happen immediately. 


That assumption drives how most Salesforce systems are built. 


A single save operation becomes responsible for validation, data updates across objects, integration callouts, notifications, enrichment logic, and whatever else the business decides to attach to it over time. 


All of it happens synchronously. All of it shares the same transaction. 


Salesforce now recommends a single-entry point per object, one mechanism as the starting point for automation on a given record change, with Flow and Apex triggers not mixed on the same object. Most orgs aren't there. Most orgs have both layered across years of accumulated requirements, none of it designed together. 


At that point, you're not dealing with automation anymore. You're dealing with a tightly coupled system that just happens to be implemented inside Salesforce. 

It works, until it doesn't. 

 


Why "Just Make It Bulkified" Is Not the Answer 


When limits start appearing, teams often reach for bulkification. Bulkification is essential. Without it, systems fail immediately. 


Bulkification doesn't solve this, though. 


It improves how work is processed, not whether the work belongs in the transaction in the first place. 


You can have perfectly bulkified logic that still fails because it executes too frequently, runs alongside too many other processes, or simply does more than the transaction can support. 


Bulkification is necessary, but it's not sufficient. It addresses efficiency, not architecture. 

 


Why Async Actually Exists 


Here's what most teams miss. 


When you move work to async, Queueables, future methods, Batch Apex, you are not just deferring execution. You are creating a new transaction. 


That new transaction gets its own CPU time (60,000ms), its own SOQL limit (200 queries), and its own DML budget. 


That's the point. 


The platform has also made this easier to access without writing Apex. The Run Asynchronously path inside a record-triggered Flow executes in a separate transaction after the original record-save has committed. For high-volume scenarios, Change Data Capture goes further. The synchronous trigger's only job is to save the record. All downstream processing runs in a fully decoupled async context. 


These aren't workarounds. They're the architecture the platform is guiding you toward. 


Async isn't about speed. It's about separation of execution contexts. 


Apex doesn't remove limits. It gives you control over where and how those limits are consumed. 


That's the difference between reacting to limits and designing with them. 

 


The Architecture the Platform Is Quietly Enforcing 


The pattern is hard to miss once you're looking for it. 


Salesforce is not just limiting you. It is guiding you. 


It is pushing you toward smaller, more controlled synchronous transactions. Toward separating core data operations from side effects. Toward moving non-critical processing into independent execution paths. Toward systems where responsibility, timing, and execution are deliberately separated. 


You've already seen this in other forms. 


Flow defines responsibility. Event-driven design defines timing. Governor limits define constraints. 


When those three are aligned, systems scale. 


When they aren't, the platform eventually forces the issue. 

 


What Happens When You Ignore the Signal 


Ignoring limits doesn't cause immediate failure. That's part of what makes them dangerous. 


The system becomes unpredictable. Failures become intermittent. Behavior varies under load. Changes feel risky. Teams hesitate to touch parts of the system they don't fully understand. 


Over time, this turns into institutional knowledge that sounds less like engineering and more like superstition. 


"Don't touch that Flow." "That object is sensitive." "It works, just leave it alone." 


At that point, the issue isn't performance. 


It's trust. 

 


The Uncomfortable Truth 


Governor limits are not there to make your life harder. 


They exist because the platform has to protect itself. In doing so, they also enforce a model of how systems should be built. 


Small transactions. Controlled execution. Clear boundaries. One entry point per object. 


Design within those constraints, and the system stays stable and predictable. 


If you don't, the platform doesn't argue with you. 


It waits. 


Usually, at the worst possible time, it reminds you. 

 


The Takeaway 


Governor limits are not a developer problem. They are not a performance issue waiting to be tuned.  


They are an architectural constraint that exists whether you acknowledge it or not. 


Treat them as something to fix after the fact, and you're solving the wrong problem. 

The real work is designing systems that don't depend on exceeding them in the first place. 

 


Where This Leaves You 


Flow defines responsibility. Events define timing. Limits define constraints. 

That's the system. Everything else is an implementation detail. 


When that lands, something else becomes clear: Most Salesforce performance problems aren't about speed. They're about architecture catching up with you. 


Most of the orgs we see didn't get here by accident. They got here by building fast without building with the platform in mind. If that's where you are, we should talk.  





 
 
 

Comments


bottom of page