Why you should care about Apex best practices
You are staring at a "Too many SOQL queries: 101" error in production at 4 PM on a Friday, and that is usually the moment Apex best practices stop being an interview topic. They are what keeps your code from breaking when the data volume grows from ten records to ten thousand.
Apex runs in a multi-tenant environment, which is a fancy way of saying we all share the same server resources. Salesforce enforces strict governor limits so one bad script can't tank the whole system, and your code has to play nice with those limits if you want to keep your sanity. Here is what actually matters in the field.
The big hitters: core Apex best practices
1. Bulkify everything you touch
This is the golden rule. I've seen teams struggle for weeks because their code worked fine for one record and then choked on a data load. Never put a SOQL query or a DML statement inside a loop. It's the fastest way to hit a limit and earn an angry call from a stakeholder.
Use collections instead: Lists, Sets, and Maps. Grab all the data you need in one query and process it in memory. Here is the right way to do it:
// The wrong way: Querying inside a loop
for(Account acc : trigger.new) {
List<Contact> cons = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}
// The right way: Use a Map to handle relationships
Set<Id> accIds = new Set<Id>();
for(Account acc : trigger.new) {
accIds.add(acc.Id);
}
Map<Id, Contact> contactMap = new Map<Id, Contact>();
for(Contact con : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accIds]) {
contactMap.put(con.AccountId, con);
}
2. One trigger per object
Don't create multiple triggers on the same object. It makes the execution order unpredictable and debugging a nightmare. Use a trigger framework to delegate your logic to handler classes, which keeps your triggers thin and your logic organized. If you're deciding between Apex vs Flow for your automation, make sure you aren't building a messy mix of both on the same object without a plan.
3. Be selective with your SOQL
Every field you query adds to your heap size, so select the fields you're actually using rather than grabbing everything in case you need it later. Use indexed fields in your WHERE clauses to keep queries fast. If your query isn't selective, Salesforce will eventually stop it from running on large tables.
Advanced patterns for production code
Handling asynchronous processing
Sometimes you have logic that takes too long or needs to talk to an external API. That's where Queueable, Batchable, and @future methods come in. In my experience, Queueable is almost always better than @future because it supports complex data types and allows for job chaining. Be careful though, because it's easy to blow through your limits if you're not tracking usage. These tips on how to stay under asynchronous Apex limits are worth a read.
Security and sharing
The "with sharing" keyword trips people up. By default, Apex runs in system mode, meaning it ignores the user's permissions, and that is a huge security risk. Always use "with sharing" unless you have a very specific reason not to. Use Security.stripInaccessible as well, so users aren't seeing or editing fields they shouldn't have access to. Building security in from the start is much easier than patching it later.
Pro tip: in a senior developer interview, don't just list the rules. Explain why they exist. Saying how one of them saved your team from a production outage shows you actually know your stuff.
Testing that actually works
Stop writing tests just to hit 75 percent coverage. That's a trap. Write tests that actually assert your business logic. Use @TestSetup to create your data once and share it across test methods. It speeds up your deployments and keeps your test code clean. And please, for the love of all things holy, don't use SeeAllData=true. It makes your tests fragile and dependent on whatever random data happens to be in the org.
Key takeaways
- Bulkify everything: no SOQL or DML in loops, ever.
- Organize your triggers: one trigger per object, with a handler framework behind it.
- Respect the limits: use the Limits class to watch your resource usage while the code runs.
- Security matters: enforce sharing rules and FLS to keep data safe.
- Test for real scenarios: focus on assertions and bulk testing, not code coverage percentages.
Wrapping it up
So what does this mean for your day-to-day work? Take an extra ten minutes to think through your data structures before you start typing, use Custom Metadata instead of hardcoding IDs, and write code that your future self won't hate when they have to fix it six months from now.
These Apex best practices are the difference between a stable system and a constant headache. Start small. If you've got a messy trigger, refactor it into a handler. If you've got a query in a loop, move it out. Your users (and your sleep schedule) will thank you.
Leave a Comment