The discipline is the same across triggers, controllers, and batch jobs. Bulkify, query selectively, run async when the work is heavy, and instrument the limits in production code.
- Bulkify every loop
Never put SOQL or DML inside a for-loop. Collect record IDs in a Set, query once, build a Map, loop over the input, single DML at the end.
- Selective-filter every query
WHERE clauses on indexed fields (Id, External ID, custom-indexed lookup) keep the scan inside the selectivity threshold. Avoid LIKE on non-indexed text fields.
- Move heavy work to async
Batchable Apex for data volumes above 50,000 records. Queueable for chained transactional work. @future for fire-and-forget callouts. Each async context gets its own governor budget.
- Instrument Limits class
Use Limits.getQueries(), Limits.getDMLStatements(), Limits.getCpuTime() in logging. Surface near-limit warnings (e.g., 80 percent of CPU) to a logging framework so you see problems before they become exceptions.
- Test with bulk data
Apex tests must insert at least 200 records (or call the method with 200 records in a list) to exercise bulk paths. Test methods that only insert one record do not detect limit failures that hit at scale.
- Governor limits cannot be increased per-org. Salesforce Support cannot raise them. Designing around them is the only path.
- Limit exceptions are uncatchable; they bypass try-catch and roll back the entire transaction. Defensive code must avoid hitting them, not handle them.
- Test methods run in a separate context with their own governor budget. Passing in test does not guarantee passing in production at higher scale.
- Flow elements count against the same SOQL and DML governor limits as Apex. A record-triggered flow with unbulkified DML hits the 150 cap as fast as bad Apex.