Top 10 Salesforce Developer Interview Questions in 2026

Published on January 8, 2026 | 15 min read | Developer Interview Prep

Let me be straight with you - Salesforce developer interviews are different from your typical software engineering interviews. Sure, you need to know how to code, but you also need to understand the Salesforce platform's quirks, limitations, and best practices. I've been on both sides of these interviews, and I can tell you that knowing Apex syntax isn't enough. You need to think like a Salesforce developer.

In this guide, I'm sharing the 10 questions that keep coming up in developer interviews, along with the kind of answers that make interviewers think, "This person gets it." These aren't just textbook answers - they're based on real conversations I've had and real code I've written in production.

What Makes Salesforce Development Different?

Before we dive into the questions, let's talk about what makes Salesforce development unique. Coming from a traditional software engineering background, I had to unlearn some habits when I started with Salesforce.

Here's what you need to wrap your head around:

The Essential Questions

10. How do you handle governor limits in batch Apex? What's your approach to designing efficient batches?

Batch Apex is where you process large amounts of data, and it's where poor coding can really hurt. I've seen batches that seemed to work fine in testing completely fail in production.

A well-designed batch class:

global class AccountUpdateBatch implements Database.Batchable { global Database.QueryLocator start(Database.BatchableContext bc) { // Keep the query simple - let Salesforce optimize it return Database.getQueryLocator([ SELECT Id, Name, Industry, AnnualRevenue FROM Account WHERE LastModifiedDate < LAST_N_DAYS:365 ]); } global void execute(Database.BatchableContext bc, List scope) { // Process this batch of records List toUpdate = new List(); for(Account acc : scope) { if(acc.AnnualRevenue > 1000000) { acc.Rating = 'Hot'; toUpdate.add(acc); } } // Use Database.update with false parameter to allow partial success if(!toUpdate.isEmpty()) { Database.SaveResult[] results = Database.update(toUpdate, false); // Handle any errors for(Integer i = 0; i < results.size(); i++) { if(!results[i].isSuccess()) { // Log the error, maybe send an email System.debug('Error updating account: ' + results[i].getErrors()); } } } } global void finish(Database.BatchableContext bc) { // Send summary email or chain another batch AsyncApexJob job = [SELECT TotalJobItems, JobItemsProcessed, NumberOfErrors FROM AsyncApexJob WHERE Id = :bc.getJobId()]; System.debug('Batch completed: ' + job.JobItemsProcessed + ' batches processed'); } }

Key practices I follow:

  • Keep batch size reasonable: Default is 200, but I might use 50-100 for complex operations
  • Use Database.update(records, false): This allows partial success - one bad record doesn't fail the whole batch
  • Always implement error handling: Log failures, don't just silently fail
  • Test with production data volumes: 200 records in test might work, but 2 million in prod might not
  • Monitor governor limits: Use System.debug to check heap size and SOQL queries used

Real story: "I inherited a batch job that was failing intermittently. Turned out it was doing SOQL queries inside the loop in the execute method. With 200 records per batch, it hit the 100 SOQL query limit. Simple refactor to query once outside the loop fixed it."

⚠️ Watch Out: Batch Apex doesn't guarantee the order records are processed. If sequence matters, you need to handle that in your code.

Common Interview Mistakes to Avoid

Let me share what I've seen candidates do that hurt their chances:

How to Prepare

Here's my two-week study plan that worked for me and others I've mentored:

Week 1 - Fundamentals Review:

Week 2 - Practice and Integration:

Every day: Write actual code. Don't just read - open up a Developer Edition org and build something. The muscle memory matters.

💡 Practice Tip: Create a personal Trailhead Playground or Developer Edition org specifically for interview prep. Build trigger frameworks, test patterns, and examples you can reference.

Questions You Should Ask Them

Remember, interviews are two-way. Here are questions that show you're thinking about more than just landing the job:

The Day Before

Here's my pre-interview checklist:

Want More Developer Resources?

Check out our certification guides, code snippets, and advanced Apex patterns.

Explore More Resources

Final Thoughts

Look, I still get nervous before technical interviews, and I've been doing this for years. That's normal. But here's what I've learned: the companies worth working for don't just want someone who can write Apex. They want someone who understands the platform, thinks about maintainability, and can communicate technical concepts clearly.

If you can explain not just what your code does, but why you wrote it that way and what alternatives you considered, you're already ahead of most candidates. If you can talk about real challenges you've faced and how you solved them, even better.

And remember - not getting a particular job doesn't mean you're not a good developer. Sometimes it's about fit, timing, or they need skills you don't have yet. Every interview makes you better for the next one.

The Salesforce ecosystem is growing fast. There are more developer jobs than there are qualified developers to fill them. If you understand the fundamentals, write clean code, and keep learning, you're going to do just fine.

💡 One More Thing: After the interview, send a thank-you email. Mention something specific you discussed. It's a small thing that makes a difference.

Have your own developer interview story or questions? We'd love to hear from you. Connect with us on our contact page and share your experience!

class="question">1. Explain the difference between a trigger and a trigger handler pattern. Why would you use a handler?

This question separates developers who just write code from those who write maintainable code. Let me show you what I mean.

A basic trigger might look like this:

trigger AccountTrigger on Account (before insert, before update) { for(Account acc : Trigger.new) { if(acc.AnnualRevenue > 1000000) { acc.Rating = 'Hot'; } } // More logic here... // And more... // Gets messy quickly }

The problem? As your org grows, this trigger becomes a nightmare. You end up with hundreds of lines of code in one file, no way to control execution order, and testing becomes painful.

The trigger handler pattern solves this:

trigger AccountTrigger on Account (before insert, before update, after insert, after update) { new AccountTriggerHandler().run(); } public class AccountTriggerHandler extends TriggerHandler { protected override void beforeInsert() { AccountService.updateRating(Trigger.new); } protected override void beforeUpdate() { AccountService.updateRating(Trigger.new); } }

Why this is better:

"In my last project, we inherited an org with triggers that were 500+ lines each. We refactored to the handler pattern and it made a huge difference in maintainability. When we needed to add new logic, we knew exactly where to put it."

2. How do you handle bulk operations in Apex? What are the common mistakes?

This is THE question that reveals whether you understand Salesforce governor limits. I've seen production code break because someone didn't think about bulk processing.

Here's what NOT to do:

trigger OpportunityTrigger on Opportunity (after insert) { for(Opportunity opp : Trigger.new) { // WRONG - SOQL in a loop! Account acc = [SELECT Id, Name FROM Account WHERE Id = :opp.AccountId]; // WRONG - DML in a loop! acc.LastOpportunityDate__c = Date.today(); update acc; } }

This code will work fine for one or two records. But the moment someone does a bulk update of 200 opportunities? You'll hit governor limits and everything fails.

Here's how to do it right:

trigger OpportunityTrigger on Opportunity (after insert) { // Collect all Account IDs first Set accountIds = new Set(); for(Opportunity opp : Trigger.new) { if(opp.AccountId != null) { accountIds.add(opp.AccountId); } } // ONE query for all accounts Map accountMap = new Map( [SELECT Id, LastOpportunityDate__c FROM Account WHERE Id IN :accountIds] ); // Update the map for(Opportunity opp : Trigger.new) { if(accountMap.containsKey(opp.AccountId)) { accountMap.get(opp.AccountId).LastOpportunityDate__c = Date.today(); } } // ONE DML operation for all accounts if(!accountMap.isEmpty()) { update accountMap.values(); } }

The key principles:

"I learned this the hard way when a data migration crashed because of triggers with queries in loops. Now I always think: what happens if 200 records hit this trigger at once?"

💡 Pro Tip: Always test your triggers with at least 200 records. That's the minimum you should handle, but good code can handle the full 10,000 records in a batch.
3. What's the difference between SOQL and SOSL? When would you use each?

This seems like a basic question, but most developers can recite the definition without really understanding when to use each one.

SOQL (Salesforce Object Query Language):

List accounts = [SELECT Id, Name, Industry FROM Account WHERE Industry = 'Technology' AND AnnualRevenue > 1000000];

Use SOQL when you know exactly which object you're querying and you need specific records based on field values. It's like SQL - very precise.

SOSL (Salesforce Object Search Language):

List> searchResults = [FIND 'Acme*' IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, Name), Lead(Id, Name)];

Use SOSL when you're doing a text search across multiple objects, like implementing a search bar in your app.

Real-world example: "I used SOSL when building a global search feature where users could type a company name and get results from Accounts, Contacts, Opportunities, and Cases. SOSL was perfect because it searched all those objects at once and returned the most relevant results first."

When NOT to use SOSL: Don't use it if you're just querying one object with specific criteria. SOQL is more efficient in those cases.

4. Explain the order of execution in Salesforce. Why does it matter?

Okay, this is one of those things that seems theoretical until it bites you in production. I've debugged countless issues that came down to not understanding execution order.

The simplified order:

  1. System validation (like required fields)
  2. Before triggers
  3. System validation (again, with the trigger changes)
  4. Duplicate rules
  5. Record is saved to database (but not committed)
  6. After triggers
  7. Assignment rules
  8. Auto-response rules
  9. Workflow rules
  10. Processes (Process Builder)
  11. Flows
  12. Escalation rules
  13. Parent rollup summary updates
  14. Commit to database

Why this matters: Let's say you have a validation rule checking if a field is filled. If your before trigger sets that field, it'll pass validation. But if you try to set it in an after trigger? Too late - validation already ran.

"I once had a bug where a workflow rule was updating a field, and then a process was trying to use the old value. Turns out, workflow rules run before processes. Understanding execution order helped me fix it by moving the logic to the right place."

⚠️ Common Gotcha: If your trigger makes DML operations (updates other records), those operations will trigger their own triggers with their own execution order. This is called "recursive triggering" and you need to handle it carefully.
5. How do you prevent recursive trigger execution?

This is a practical problem every Salesforce developer faces. Without proper handling, triggers can call themselves repeatedly until you hit governor limits.

The classic static variable approach:

public class TriggerHelper { private static Boolean hasRun = false; public static Boolean isFirstRun() { if(!hasRun) { hasRun = true; return true; } return false; } public static void reset() { hasRun = false; } } // In your trigger if(TriggerHelper.isFirstRun()) { // Your logic here }

A more sophisticated approach using Sets:

public class TriggerHelper { private static Set processedIds = new Set(); public static Boolean isProcessed(Id recordId) { if(processedIds.contains(recordId)) { return true; } processedIds.add(recordId); return false; } }

This is better because it tracks which specific records have been processed, not just whether the trigger has run once.

"The Set approach saved me when we had a complex trigger that updated related records, which in turn updated the original record. By tracking IDs, we only processed each record once per transaction."

6. What are the different ways to call Apex from Lightning Web Components?

This is essential if you're doing any modern Salesforce development. The days of Visualforce are fading, and LWC is where everything's heading.

Using @wire for reactive data:

// In your LWC JavaScript import { LightningElement, wire } from 'lwc'; import getAccounts from '@salesforce/apex/AccountController.getAccounts'; export default class AccountList extends LightningElement { @wire(getAccounts) accounts; }

Use @wire when you want the data to automatically refresh when dependencies change. It's reactive and efficient.

Using imperative calls for actions:

import { LightningElement } from 'lwc'; import saveAccount from '@salesforce/apex/AccountController.saveAccount'; export default class AccountForm extends LightningElement { handleSave() { saveAccount({ accRecord: this.accountData }) .then(result => { // Handle success }) .catch(error => { // Handle error }); } }

Use imperative calls when you need to trigger something based on user action, like clicking a button.

Key difference: @wire is for getting data that might change. Imperative is for "do this thing now."

"I use @wire for displaying lists and viewing data. I use imperative calls for saves, deletes, or any operation where I need to control exactly when it happens and handle the response specifically."

7. How do you write test classes? What's your testing strategy?

Here's where I see the biggest difference between junior and senior developers. Anyone can write a test that gets to 75% coverage. Good developers write tests that actually catch bugs.

A basic test (not good enough):

@isTest public class AccountTriggerTest { @isTest static void testInsert() { Account acc = new Account(Name = 'Test'); insert acc; // No assertions - just hits code } }

A proper test:

@isTest public class AccountTriggerTest { @TestSetup static void setup() { // Create test data once for all test methods List accounts = new List(); for(Integer i = 0; i < 200; i++) { accounts.add(new Account( Name = 'Test ' + i, AnnualRevenue = 2000000 )); } insert accounts; } @isTest static void testBulkInsert() { Test.startTest(); List newAccounts = new List(); for(Integer i = 0; i < 200; i++) { newAccounts.add(new Account( Name = 'Bulk Test ' + i, AnnualRevenue = 1500000 )); } insert newAccounts; Test.stopTest(); // Verify the trigger worked List inserted = [SELECT Rating FROM Account WHERE Name LIKE 'Bulk Test%']; for(Account acc : inserted) { System.assertEquals('Hot', acc.Rating, 'Rating should be Hot for high revenue accounts'); } } @isTest static void testNegativeCase() { Account lowRevenue = new Account( Name = 'Small Company', AnnualRevenue = 50000 ); insert lowRevenue; Account result = [SELECT Rating FROM Account WHERE Id = :lowRevenue.Id]; System.assertNotEquals('Hot', result.Rating, 'Rating should not be Hot for low revenue'); } }

My testing principles:

"I've seen orgs with 90% code coverage and still full of bugs. Coverage doesn't matter if your tests don't actually verify behavior."

💡 Testing Tip: Write your tests before you write your code (TDD). It forces you to think about what your code should do, not just what it does do.
8. Explain Database.Savepoint and rollback. When would you use them?

This is for more advanced scenarios where you need transaction control - basically, the ability to undo things if something goes wrong.

Here's how it works:

public static void processRecords() { Savepoint sp = Database.setSavepoint(); try { // Do some DML insert newAccounts; // Do more DML that might fail insert newOpportunities; // If we get here, everything worked } catch(Exception e) { // Something went wrong, undo everything Database.rollback(sp); // Handle the error appropriately throw e; } }

Real-world example: "I used this when building a process that created an Account, Contact, Opportunity, and Case all at once. If any of those failed, I didn't want partial data - it was all or nothing. The savepoint let me roll everything back if any step failed."

Important to know:

9. What's the difference between future methods and Queueable Apex?

Both let you run code asynchronously, but Queueable is newer and more powerful. Let me show you why I almost always use Queueable now.

Future method (the old way):

@future(callout=true) public static void makeCallout(Set accountIds) { // Can't pass complex objects // Can't chain to another future method // No way to monitor progress }

Queueable (the better way):

public class CalloutQueueable implements Queueable, Database.AllowsCallouts { private List accounts; public CalloutQueueable(List accs) { this.accounts = accs; // Can pass complex objects! } public void execute(QueueableContext context) { // Make your callout // Can chain another queueable job if(moreWork) { System.enqueueJob(new AnotherQueueable()); } } } // Calling it System.enqueueJob(new CalloutQueueable(accounts));

Why Queueable is better:

"The only time I use @future now is when I'm maintaining legacy code. For new development, Queueable is the way to go."