The Gotchas of Salesforce Development: Navigating Governor Limits and Platform Constraints

At Zaghop Consulting, we live and breathe Salesforce. Whether we're building custom apps, integrating external systems, or helping clients streamline operations, we always keep one thing in mind: Salesforce is a multitenant platform - and that means rules. Lots of them.

If you're a developer transitioning from traditional web development to Salesforce, these “governor limits” can feel like hidden traps. And even seasoned developers can get burned by edge cases. Here’s a breakdown of the key limits and gotchas we navigate every day - and how to avoid them.

1. DML Limits: 150 Per Transaction

You can only perform 150 DML operations (e.g., insert, update, delete, undelete) per transaction. Sounds like plenty - until you’re working in a loop or invoking multiple triggers.

Gotcha: Bulk triggers that do one DML per record will blow this limit fast.
Fix: Use List<Record> and perform bulk DML outside the loop.

2. SOQL Query Limits: 100 Per Transaction

Salesforce allows up to 100 SOQL queries per transaction.

Gotcha: Triggers calling helper methods that perform queries in loops.
Fix: Move SOQL out of loops, leverage maps to cache lookups, and combine queries where possible.

3. CPU Time Limit: 10,000ms

Each transaction has a CPU time limit - 10 seconds for synchronous Apex.

Gotcha: Complex logic, large data sets, or nested loops can silently push you over this threshold.
Fix: Refactor into queueable or batch jobs, offload to asynchronous methods, and avoid inefficient recursive logic.

4. Heap Size Limit: 6 MB (Synchronous), 12 MB (Async)

This governs how much memory your transaction can use in RAM.

Gotcha: Too many large objects, especially when deserializing JSON or querying rich relationships.
Fix: Query only required fields, paginate data, and clear large objects after use.

5. Queueable Job Chaining Limit: 1 Child Job

You can only enqueue one queueable job from another queueable context.

Gotcha: You can't dynamically spawn multiple queueables from within a queueable job.
Fix: Chain just one, or escalate to batch jobs for more flexibility.

6. Too Many Future Calls: Max 50 Per Transaction

@future methods are convenient, but you can only call 50 of them in one go.

Gotcha: Calling one @future method inside a loop can hit this quickly.
Fix: Avoid future-in-loops, use batch/queueable instead, or combine calls into a single method.

7. Uncommitted Work Pending

You can’t call certain async operations like System.enqueueJob or Database.executeBatch after performing a DML operation in the same transaction.

Gotcha: Calling an async method from within a trigger or after a DML statement.
Fix: Move the async call into a post-commit logic layer (like a Platform Event, Change Data Capture, or Scheduled Job).

8. Callout Limits and Constraints

  • Only 100 callouts per transaction
  • Callouts can't be made after a DML operation in synchronous code
  • 120-second timeout max

Gotcha: External integrations that require a callout after save logic.
Fix: Use @future(callout=true) or move to queueable architecture.

9. Trigger Recursion

Salesforce doesn’t prevent infinite loops in triggers - it’s on you.

Gotcha: An update trigger causes another update, and so on.
Fix: Use static variables in Apex to track and prevent recursion.

10. Field History Tracking: 20 Fields Max

Field history tracking is limited to 20 fields per object.

Gotcha: Clients assume every field can be audited.
Fix: Use custom objects or third-party audit tools for full change logs.

11. Limits in Test Classes

  • Max 2000 records created per test
  • Async limits are stricter
  • Test classes do not commit DML permanently

Gotcha: Tests pass locally but fail in deployment due to shared limits.
Fix: Break up tests, use Test.startTest() and Test.stopTest(), and always assert selectively.

12. Cross-Namespace Challenges

If you're building managed packages or AppExchange apps, cross-namespace limits apply.

Gotcha: Accessing fields, classes, or methods across namespaces requires explicit exposure.
Fix: Use global visibility and careful API design for extension.

Final Thoughts: Know the Playground You’re In

Salesforce development isn’t about coding without limits - it’s about coding within constraints. That’s the art. At Zaghop, we see these limits not as roadblocks, but as signals - cues to write more efficient, scalable, and platform-native solutions.

Got a Salesforce “gotcha” that burned you? We’ve probably fixed it. Let’s talk.