Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
A developer reviewing Apex code snippets demonstrating efficient coding practices and best practices.
Apex

Apex Best Practices - Salesforce Developer Interview Guide

Ever hit a SOQL 101 error on a Friday afternoon? Here are the Apex best practices that keep your code scalable, and that come up in almost every Salesforce developer interview.

The short answer

Apex best practices are the coding and design standards that keep a Salesforce application scalable, secure, and inside governor limits on a multi-tenant platform. This guide covers bulkification, one trigger per object, selective queries, asynchronous processing, and unit testing.

Key takeaways Bulkify every database operation: query once, work on collections in memory, and keep SOQL and DML out of loops. Keep one trigger per object and push the business logic into handler classes, so the execution order stays predictable. Select only the fields you use and filter on indexed fields, which keeps heap size down and queries fast. Enforce record and field security with the with sharing keyword and Security.stripInaccessible. Build isolated test records in a @TestSetup method and assert on business logic instead of leaning on SeeAllData=true.

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.

Frequently asked questions

Why should you have only one trigger per object?

Salesforce does not guarantee which trigger runs first when an object has several of them. One trigger that hands its logic to handler classes gives you a predictable order and much easier debugging.

How do you avoid SOQL queries inside loops in Apex?

Collect the record IDs into a Set and run one SOQL query outside the loop. Put the results into a Map so you can match and process the records in memory.

Why is Queueable Apex better than @future methods?

Queueable Apex takes complex data types and lets you chain jobs, which gives you more room for long-running work and external callouts.

How do you enforce object and field-level security in Apex?

Declare the class with sharing so record-level access rules apply, and call Security.stripInaccessible so users cannot view or edit fields they have no access to.

Why should you avoid SeeAllData=true in Apex test classes?

With SeeAllData=true your tests depend on whatever data already sits in the org, so they break for reasons that have nothing to do with your code. Create isolated test data in a @TestSetup method instead.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment