Apex Best Practices – Salesforce Developer Interview Guide

Why you should care about Apex best practices

Look, we’ve all been there. You’re staring at a “Too many SOQL queries: 101” error in production at 4 PM on a Friday. It’s usually at this exact moment that the value of Apex best practices becomes painfully clear. Following these isn’t just about passing a technical interview or making your code look pretty. It’s about building stuff that doesn’t break when your data volume grows from ten records to ten thousand.

Apex runs in a multi-tenant environment, which is just a fancy way of saying we’re all sharing the same server resources. Salesforce enforces strict governor limits to make sure one bad script doesn’t tank the whole system. If you want to stay employed and keep your sanity, you need to write code that plays nice with these limits. Let’s break down the stuff that actually matters in the real world.

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 they wrote code that worked fine for one record but choked on a data load. Never, ever put a SOQL query or a DML statement inside a loop. It’s the fastest way to hit a limit and get an angry call from a stakeholder.

Instead, use collections like Lists, Sets, and Maps. Grab all the data you need in one query and process it in memory. Here’s a quick look at 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

I can’t stress this enough: 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. This 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. Don’t be lazy and query every field just because you might need them later. Only select the fields you’re actually using. Also, use indexed fields in your WHERE clauses to keep your 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, or @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. But be careful – it’s easy to blow through your limits if you’re not tracking usage. You should definitely check out these tips on how to stay under asynchronous Apex limits to avoid getting stuck.

Security and sharing

One thing that trips people up is the “with sharing” keyword. By default, Apex runs in system mode, meaning it ignores the user’s permissions. This is a huge security risk. Always use “with sharing” unless you have a very specific reason not to. Also, use Security.stripInaccessible to make sure users aren’t seeing or editing fields they shouldn’t have access to. It’s much easier to build security in from the start than to try and patch it later.

Pro tip: When you’re in a senior developer interview, don’t just list the rules. Explain the “why.” Mentioning how a specific practice 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 use it across multiple 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: Use one trigger per object and a handler framework.
  • Respect the limits: Use the Limits class to monitor your resource usage in real-time.
  • Security matters: Enforce sharing rules and FLS to keep data safe.
  • Test for real scenarios: Focus on assertions and bulk testing, not just code coverage percentages.

Wrapping it up

So, what does this actually mean for your day-to-day work? It means taking an extra ten minutes to think through your data structures before you start typing. It means using Custom Metadata instead of hardcoding IDs. And it means writing code that your future self won’t hate when they have to fix it six months from now.

Following these Apex best practices isn’t just a suggestion – it’s 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.