Salesforce Apex Interview Questions and Prep Guide

In this blog post we are going to cover some of the most important topics that are covered as part of Salesforce Apex Interview Questions. The curated questions cover the majority of topics in Salesforce Apex along with example and code snippets, such that it leaves a lasting impression on the readers mind.

To get the best out of this article, readers are advised not to treat it as another Salesforce Apex interview question bank but as a study material. Each question also has reference to Salesforce documentation around that subject, so that the readers can go further deep into any specific topic.

1. What is Apex in Salesforce?

Apex is a strongly-typed, object-oriented programming language developed by Salesforce. It allows developers to execute flow and transaction control statements on Salesforce servers in conjunction with calls to the API. Apex syntax is similar to Java and acts as the server-side language in the Salesforce ecosystem.

Example

public class HelloWorld {
    public static void sayHello() {
        System.debug('Hello, Salesforce!');
    }
}

Documentation: Apex Developer Guide

2. What are Triggers in Salesforce? How are they used?

Apex Triggers are pieces of code that execute before or after specific database events, such as insertions, updates, or deletions of records. They allow developers to perform custom actions when records are manipulated in Salesforce.

Example:

trigger AccountTrigger on Account (before insert, after insert, before update, after update) {
    if (Trigger.isBefore) {
        if (Trigger.isInsert) {
            // Before insert logic
        } else if (Trigger.isUpdate) {
            // Before update logic
        }
    } else if (Trigger.isAfter) {
        if (Trigger.isInsert) {
            // After insert logic
        } else if (Trigger.isUpdate) {
            // After update logic
        }
    }
}

Documentation: Apex Triggers

3. What is a Flow ?

A declarative automation tool that allows administrators to create automated processes using a graphical interface. Flows can be used to collect data, create and update records, send emails, and more, without writing code.

Example:

  • A Flow can be created using the Flow Builder in Salesforce, where you can drag and drop elements to define the steps of your process.
  • Flows can be triggered automatically based on certain conditions, scheduled to run at specific times, or manually started by users.

4. When should you use a Trigger instead of a Flow?

  • Use a Trigger:
    • When you need to handle complex business logic that involves multiple DML operations.
    • When performing bulk operations that require iteration over records and bulk processing.
    • When you need fine-grained control over the order of operations (before or after a specific event).
    • When there are performance considerations that cannot be handled efficiently by Flows.
  • Use a Flow:
    • When the logic is simple and can be configured declaratively without writing code.
    • When the process involves user interactions, such as forms or data collection.
    • When the automation needs to be easily maintainable and understandable by administrators without a development background.
    • When you want to leverage out-of-the-box functionality provided by Salesforce, like sending emails, creating tasks, or posting to Chatter.

5. What are the advantages of using Flows over Triggers?

  • Advantages of Flows:
    • Declarative Approach: Flows provide a no-code solution, allowing administrators to build and maintain processes without writing code.
    • Ease of Use: The graphical user interface of Flow Builder makes it easier to visualize and design automation processes.
    • Maintenance: Flows are easier to understand and maintain, especially for administrators who may not have a coding background.
    • Reusability: Elements within Flows can be reused across multiple Flows, and changes to these elements can be made centrally.
    • Error Handling: Flows provide built-in error handling features that make it easier to handle exceptions and notify users.
  • Documentation: Flow Builder

6. Can you provide an example where a Trigger is necessary and a Flow might not suffice?

Scenario 1: Suppose you want to stop a record from getting inserted based on certain conditions and show an error message in the UI. How will you achieve that.

Before Insert Trigger

trigger ContactBeforeInsert on Contact (before insert) {
    ContactTriggerHandler.beforeInsert(Trigger.new);
       // Create a set to hold all the emails from the new contacts
        Set<String> emailSet = new Set<String>();
        
        // Populate the set with emails from the new contacts
        for (Contact c : Trigger.new) {
            if (c.Email != null) {
                emailSet.add(c.Email);
            }
        }
        
        // Query for existing contacts with these emails
        Map<String, Contact> existingContactsMap = new Map<String, Contact>();
        for (Contact c : [SELECT Email FROM Contact WHERE Email IN :emailSet]) {
            existingContactsMap.put(c.Email, c);
        }
        
        // Check if any of the new contacts have an email that already exists
        for (Contact c : Trigger.new) {
            if (c.Email != null && existingContactsMap.containsKey(c.Email)) {
                c.addError('A contact with this email already exists: ' + c.Email);
            }
        }
}

Explanation

  1. Trigger: The above trigger logic is executed before a new Contact record is inserted.
  2. Set Creation: A Set is created to store the emails of the new Contact records being inserted.
  3. Query Existing Contacts: The handler queries the existing Contact records to check if any of the emails from the new records already exist.
  4. Validation: For each new Contact, if its email exists in the queried records, an error is added to the record using the addError method. This causes the insert operation to fail and displays the error in the UI.

Scenario 2: Suppose you need to update the OwnerId of a related Contact whenever the OwnerId of an Account is changed. Additionally, if the OwnerId is changed to a specific user, you also need to update a custom field SpecialHandling__c on all related Opportunities.

Trigger Example

trigger AccountOwnerChange on Account (after update) {
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : Trigger.new) {
        if (acc.OwnerId != Trigger.oldMap.get(acc.Id).OwnerId) {
            accountIds.add(acc.Id);
        }
    }

    List<Contact> contactsToUpdate = [SELECT Id, AccountId, OwnerId FROM Contact WHERE AccountId IN :accountIds];
    for (Contact con : contactsToUpdate) {
        con.OwnerId = Trigger.newMap.get(con.AccountId).OwnerId;
    }
    update contactsToUpdate;

    List<Opportunity> oppsToUpdate = [SELECT Id, AccountId, SpecialHandling__c FROM Opportunity WHERE AccountId IN :accountIds];
    for (Opportunity opp : oppsToUpdate) {
        if (Trigger.newMap.get(opp.AccountId).OwnerId == '005xx000001Sv6mAAC') { // Specific User ID
            opp.SpecialHandling__c = true;
        }
    }
    update oppsToUpdate;
}

This scenario involves complex logic with multiple DML operations, conditional checks, and updates on related objects, which are more efficiently handled by a Trigger.

7. What are context variables in a Salesforce trigger?

Context variables in Salesforce triggers are special variables that provide information about the state of the records that are being processed by the trigger. They allow developers to determine the context in which the trigger is being executed (e.g., before insert, after update) and access the records that are being processed.

  • Common Context Variables:
    • Trigger.isBefore
    • Trigger.isAfter
    • Trigger.isInsert
    • Trigger.isUpdate
    • Trigger.isDelete
    • Trigger.isUndelete
    • Trigger.new
    • Trigger.old
    • Trigger.newMap
    • Trigger.oldMap
    • Trigger.size
  • Documentation: Apex Triggers

8. What is the difference between Trigger.new and Trigger.old?

  • Trigger.new: A list of the new versions of the records that are being processed by the trigger. It is available in insert, update, and undelete triggers.
trigger ExampleTrigger on Account (before insert, before update) {
    for (Account acc : Trigger.new) {
        System.debug('New Account Name: ' + acc.Name);
    }
}

Trigger.old: A list of the old versions of the records that are being processed by the trigger. It is only available in update and delete triggers.

trigger ExampleTrigger on Account (before delete, before update) {
    for (Account acc : Trigger.old) {
        System.debug('Old Account Name: ' + acc.Name);
    }
}

Documentation: Trigger Context Variables

9. When would you use Trigger.newMap and Trigger.oldMap?

  • Trigger.newMap: A map of IDs to the new versions of the records. It is available in insert, update, and undelete triggers. It is useful when you need to access a specific record by its ID.
trigger ExampleTrigger on Account (after update) {
    for (Id accId : Trigger.newMap.keySet()) {
        Account newAcc = Trigger.newMap.get(accId);
        System.debug('New Account Name: ' + newAcc.Name);
    }
}

Trigger.oldMap: A map of IDs to the old versions of the records. It is available in update and delete triggers. It is useful when you need to access a specific record by its ID.

trigger ExampleTrigger on Account (before delete) {
    for (Id accId : Trigger.oldMap.keySet()) {
        Account oldAcc = Trigger.oldMap.get(accId);
        System.debug('Old Account Name: ' + oldAcc.Name);
    }
}

10. How can you use Trigger.isInsert and Trigger.isUpdate within a single trigger?

You can use Trigger.isInsert and Trigger.isUpdate to determine the context of the trigger execution and execute different logic for each scenario.

  • Example:
trigger ExampleTrigger on Account (before insert, before update) {
    if (Trigger.isInsert) {
        for (Account acc : Trigger.new) {
            acc.Description = 'New Account';
        }
    } else if (Trigger.isUpdate) {
        for (Account acc : Trigger.new) {
            Account oldAcc = Trigger.oldMap.get(acc.Id);
            if (acc.Name != oldAcc.Name) {
                acc.Description = 'Account Name Changed';
            }
        }
    }
}

11. Explain the use of Trigger.isBefore and Trigger.isAfter.

  • Trigger.isBefore: Indicates that the trigger is executing before the record is saved to the database. This is used for validation or to set values before saving the record.
trigger ExampleTrigger on Account (before insert, before update) {
    if (Trigger.isBefore) {
        for (Account acc : Trigger.new) {
            acc.Description = 'This is set before save';
        }
    }
}

Trigger.isAfter: Indicates that the trigger is executing after the record has been saved to the database. This is used to perform actions that require the record ID, such as creating related records.

trigger ExampleTrigger on Account (after insert, after update) {
    if (Trigger.isAfter) {
        List<Opportunity> opps = new List<Opportunity>();
        for (Account acc : Trigger.new) {
            opps.add(new Opportunity(Name = acc.Name + ' Opportunity', AccountId = acc.Id, CloseDate = Date.today().addMonths(1), StageName = 'Prospecting'));
        }
        insert opps;
    }
}

12. What is Trigger.size and how can it be used?

Trigger.size returns the total number of records in the Trigger.new list. This is useful for understanding the bulk size of the operation and applying logic based on the number of records.

  • Example:
trigger ExampleTrigger on Account (before insert, before update) {
    System.debug('Total number of records in this trigger execution: ' + Trigger.size);
    if (Trigger.size > 10) {
        for (Account acc : Trigger.new) {
            acc.Description = 'Bulk Update';
        }
    } else {
        for (Account acc : Trigger.new) {
            acc.Description = 'Single Update';
        }
    }
}

13. How can you use Trigger.isDelete to handle record deletions?

Trigger.isDelete checks if the trigger is executing during a delete operation. It is used to perform actions when records are deleted, such as logging deletions or preventing deletions under certain conditions.

  • Example:
trigger ExampleTrigger on Account (before delete) {
    if (Trigger.isDelete) {
        for (Account acc : Trigger.old) {
            if (acc.Type == 'Key Account') {
                acc.addError('Key Accounts cannot be deleted.');
            }
        }
    }
}

14. In what scenario would you use Trigger.isUndelete?

Trigger.isUndelete is used in triggers that execute when records are restored from the Recycle Bin. This is useful for reestablishing relationships or restoring data integrity when a record is undeleted.

  • Example:
trigger ExampleTrigger on Account (after undelete) {
    if (Trigger.isUndelete) {
        for (Account acc : Trigger.new) {
            acc.Description = 'This account was restored';
        }
        update Trigger.new;
    }
}

15. What is the purpose of Trigger.oldMap in an update trigger?

Trigger.oldMap provides a map of the IDs to the old versions of the records before they were updated. It is used to compare old and new values to determine what changes have been made.

  • Example:
trigger ExampleTrigger on Account (before update) {
    for (Account newAcc : Trigger.new) {
        Account oldAcc = Trigger.oldMap.get(newAcc.Id);
        if (newAcc.Name != oldAcc.Name) {
            System.debug('Account name changed from ' + oldAcc.Name + ' to ' + newAcc.Name);
        }
    }
}

16. How would you use context variables to bulkify a trigger?

To bulkify a trigger, you should operate on collections of records instead of individual records. Using context variables like Trigger.new, Trigger.old, Trigger.newMap, and Trigger.oldMap helps ensure that your trigger can handle bulk DML operations efficiently.

  • Example:
trigger ExampleTrigger on Account (before update) {
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : Trigger.new) {
        accountIds.add(acc.Id);
    }
    
    List<Contact> contactsToUpdate = new List<Contact>();
    for (Contact con : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
        con.Description = 'Updated due to account update';
        contactsToUpdate.add(con);
    }
    
    if (!contactsToUpdate.isEmpty()) {
        update contactsToUpdate;
    }
}

Documentation: Apex Triggers

17. What is Queueable Apex in Salesforce?

Queueable Apex is a way to run asynchronous Apex jobs similar to future methods but with the added benefit of job chaining, more complex parameter passing, and improved debugging capabilities. It allows developers to run background jobs that are processed in a separate thread and provide a more flexible and powerful way to handle asynchronous operations.

  • Example:
public class MyQueueableClass implements Queueable {
    public void execute(QueueableContext context) {
        // Your logic here
        System.debug('Executing Queueable Apex');
    }
}

18. How do you enqueue a Queueable job?

To enqueue a Queueable job, you use the System.enqueueJob method, passing an instance of the class that implements the Queueable interface.

  • Example:
ID jobId = System.enqueueJob(new MyQueueableClass());
System.debug('Enqueued Job ID: ' + jobId);

19. What are the advantages of using Queueable Apex over Future methods?

  • Chaining Jobs: Queueable jobs can be chained by enqueuing another job from the execute method.
  • Complex Objects: Queueable Apex allows passing complex types such as custom objects, SObjects, and collections, whereas future methods only support primitive data types.
  • Monitoring and Debugging: Queueable jobs are easier to monitor and debug using the Apex Job Queue in Salesforce.
  • Job ID: The System.enqueueJob method returns a job ID that can be used to track the job status.
  • Documentation: Future Methods vs Queueable

20. How can you chain Queueable jobs in Salesforce?

Chaining Queueable jobs is achieved by enqueuing a new Queueable job from within the execute method of another Queueable job.

  • Example:
public class FirstQueueableJob implements Queueable {
    public void execute(QueueableContext context) {
        System.debug('First job executed.');
        System.enqueueJob(new SecondQueueableJob());
    }
}

public class SecondQueueableJob implements Queueable {
    public void execute(QueueableContext context) {
        System.debug('Second job executed.');
    }
}

// Enqueue the first job to start the chain
ID jobId = System.enqueueJob(new FirstQueueableJob());

21. Can you pass parameters to a Queueable job? If so, how?

Yes, you can pass parameters to a Queueable job by defining instance variables in the class that implements the Queueable interface and initializing them through the constructor.

  • Example:
public class MyQueueableJob implements Queueable {
    private String param1;
    private Integer param2;

    public MyQueueableJob(String p1, Integer p2) {
        this.param1 = p1;
        this.param2 = p2;
    }

    public void execute(QueueableContext context) {
        System.debug('Parameter 1: ' + param1);
        System.debug('Parameter 2: ' + param2);
    }
}

// Enqueue the job with parameters
ID jobId = System.enqueueJob(new MyQueueableJob('Test', 123));

22. How can you handle exceptions in a Queueable job?

Exceptions in a Queueable job can be handled using standard try-catch blocks within the execute method. It’s important to log or handle the exceptions appropriately to ensure that the job does not fail silently.

  • Example:
public class MyQueueableJob implements Queueable {
    public void execute(QueueableContext context) {
        try {
            // Your logic here
            Integer result = 10 / 0; // This will cause an exception
        } catch (Exception e) {
            System.debug('Exception caught: ' + e.getMessage());
            // Handle exception, e.g., send an email, log to a custom object, etc.
        }
    }
}

// Enqueue the job
ID jobId = System.enqueueJob(new MyQueueableJob());

23. What is the maximum number of Queueable jobs that can be added to the queue in a single transaction?

The maximum number of Queueable jobs that can be added to the queue with System.enqueueJob in a single transaction is 50.

24. How can you monitor the status of a Queueable job?

The status of a Queueable job can be monitored using the AsyncApexJob object. You can query this object to get the status of the job using the job ID returned by System.enqueueJob.

  • Example:
// Enqueue the job
ID jobId = System.enqueueJob(new MyQueueableJob());

// Query the status of the job
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, CompletedDate, MethodName FROM AsyncApexJob WHERE Id = :jobId];
System.debug('Job Status: ' + job.Status);

25. Can Queueable jobs be scheduled? If yes, how?

Yes, Queueable jobs can be scheduled by invoking System.enqueueJob from within a Schedulable class. This allows you to schedule the execution of a Queueable job at a specific time.

  • Example:
public class MySchedulableClass implements Schedulable {
    public void execute(SchedulableContext context) {
        System.enqueueJob(new MyQueueableJob());
    }
}

// Schedule the job
String cronExp = '0 0 12 * * ?'; // Schedule to run at noon every day
System.schedule('Daily Queueable Job', cronExp, new MySchedulableClass());

26. What are some common use cases for Queueable Apex?

Common use cases for Queueable Apex include:

  • Long-Running Processes: Operations that take a long time to complete, such as complex calculations or data processing.
  • Chained Jobs: Executing dependent jobs sequentially.
  • Integration: Making callouts to external services and processing the response asynchronously.
  • Bulk Processing: Processing large volumes of data without hitting governor limits.
  • Documentation: Queueable Apex Use Cases

27. What is a Schedulable class in Salesforce?

A Schedulable class in Salesforce allows you to run Apex code at specified times. You implement the Schedulable interface to define the execute method, which contains the code to be run.

  • Example:
public class MySchedulableClass implements Schedulable {
    public void execute(SchedulableContext context) {
        System.debug('Scheduled Apex executed.');
    }
}

28. How do you schedule a Schedulable class to run at a specific time?

You schedule a Schedulable class by using the System.schedule method, passing the job name, a Cron expression to specify the schedule, and an instance of the Schedulable class.

  • Example:
String cronExp = '0 0 12 * * ?'; // Every day at noon
System.schedule('Daily Job', cronExp, new MySchedulableClass());

29. What is a Cron expression and how is it used in scheduling Apex?

A Cron expression is a string that represents a schedule to execute a job. It consists of six fields: seconds, minutes, hours, day of month, month, and day of week. Salesforce uses Cron expressions to define the timing of scheduled jobs.

  • Example:
String cronExp = '0 0 12 * * ?'; // Every day at noon
System.schedule('Daily Job', cronExp, new MySchedulableClass());

30. How can you monitor and manage scheduled jobs in Salesforce?

Answer: You can monitor and manage scheduled jobs using the Scheduled Jobs page in Salesforce Setup or by querying the CronTrigger and CronJobDetail objects.

  • Example
// Query to get scheduled jobs
List<CronTrigger> cronTriggers = [SELECT Id, CronJobDetail.Name, NextFireTime FROM CronTrigger];
for (CronTrigger ct : cronTriggers) {
    System.debug('Scheduled Job: ' + ct.CronJobDetail.Name + ', Next Run Time: ' + ct.NextFireTime);
}

31. Can you schedule a job to run more frequently than once per hour?

Answer: No, Salesforce does not support scheduling a job to run more frequently than once per hour using a single Cron expression. However, you can create multiple schedules with different start times to achieve more frequent execution.

  • Example:
// Schedule job to run every hour
String cronExp1 = '0 0 * * * ?'; // On the hour
String cronExp2 = '0 30 * * * ?'; // On the half-hour
System.schedule('Hourly Job 1', cronExp1, new MySchedulableClass());
System.schedule('Hourly Job 2', cronExp2, new MySchedulableClass());

32. How do you unschedule a scheduled job?

You can unschedule a job using the System.abortJob method with the job ID retrieved from the CronTrigger object.

  • Example:
// Query to get the job ID
CronTrigger ct = [SELECT Id FROM CronTrigger WHERE CronJobDetail.Name = 'Daily Job'];
// Unschedule the job
System.abortJob(ct.Id);

33. What are the governor limits for scheduled Apex?

Governor limits for scheduled Apex include:

  • Maximum number of scheduled Apex jobs: 100 at a time.
  • Maximum batch executions: 5 concurrent batches.
  • Total number of jobs queued per 24 hours: 250,000.
  • Documentation: Governor Limits

34. How can you chain scheduled jobs in Apex?

You can chain scheduled jobs by scheduling a new job from the execute method of a Schedulable class. This allows creating a sequence of jobs.

  • Example:
public class FirstSchedulableJob implements Schedulable {
    public void execute(SchedulableContext context) {
        System.debug('First job executed.');
        // Schedule the next job
        String cronExp = '0 0 1 * * ?'; // Next day at 1 AM
        System.schedule('Second Job', cronExp, new SecondSchedulableJob());
    }
}

public class SecondSchedulableJob implements Schedulable {
    public void execute(SchedulableContext context) {
        System.debug('Second job executed.');
    }
}

35. What is the difference between Schedulable and Batchable classes?

Schedulable classes are designed to run Apex code at specified times, whereas Batchable classes are designed to handle large volumes of data by breaking it into smaller chunks and processing asynchronously. Batchable classes can also be scheduled using Schedulable.

  • Example:
public class MyBatchClass implements Database.Batchable<SObject> {
    // Batchable methods
}

public class MySchedulableClass implements Schedulable {
    public void execute(SchedulableContext context) {
        Database.executeBatch(new MyBatchClass(), 200);
    }
}

36. Can you pass parameters to a Schedulable class? If so, how?

Yes, you can pass parameters to a Schedulable class by defining instance variables and setting them through the constructor or setter methods before scheduling the job.

  • Example:
public class MySchedulableClass implements Schedulable {
    private String param;

    public MySchedulableClass(String param) {
        this.param = param;
    }

    public void execute(SchedulableContext context) {
        System.debug('Parameter: ' + param);
    }
}

// Schedule the job with parameter
String cronExp = '0 0 12 * * ?';
MySchedulableClass job = new MySchedulableClass('Test Parameter');
System.schedule('Parameterized Job', cronExp, job);

Documentation: Passing Parameters

37. What is the Comparable interface in Apex and how is it used?

The Comparable interface in Apex allows you to define a natural ordering for objects of a class. Classes that implement this interface must define the compareTo method, which compares the current instance with another instance of the same type.

  • Example:
public class MyClass implements Comparable {
    public Integer value;

    public MyClass(Integer value) {
        this.value = value;
    }

    public Integer compareTo(Object compareTo) {
        MyClass other = (MyClass)compareTo;
        if (this.value == other.value) return 0;
        return this.value > other.value ? 1 : -1;
    }
}

// Usage
List<MyClass> myList = new List<MyClass>{ new MyClass(5), new MyClass(1), new MyClass(3) };
myList.sort();

38. How does the compareTo method work in the Comparable interface?

The compareTo method compares the current instance with another instance and returns:

  • 0 if both instances are equal.
  • 1 if the current instance is greater.
  • -1 if the current instance is less.
  • Example:
public Integer compareTo(Object compareTo) {
    MyClass other = (MyClass)compareTo;
    if (this.value == other.value) return 0;
    return this.value > other.value ? 1 : -1;
}

39. What is the Comparator interface in Apex and how is it used?

The Comparator interface allows you to define custom comparison logic for sorting objects. Classes that implement this interface must define the compare method, which takes two objects as parameters and returns an integer based on their comparison.

  • Example:
public class MyComparator implements Comparator {
    public Integer compare(Object obj1, Object obj2) {
        MyClass a = (MyClass)obj1;
        MyClass b = (MyClass)obj2;
        if (a.value == b.value) return 0;
        return a.value > b.value ? 1 : -1;
    }
}

// Usage
List<MyClass> myList = new List<MyClass>{ new MyClass(5), new MyClass(1), new MyClass(3) };
myList.sort(new MyComparator());

40. How do you sort a list of objects using the Comparator interface?

To sort a list of objects using the Comparator interface, you define a comparator class that implements the Comparator interface and then pass an instance of this class to the sort method of the list.

  • Example:
public class MyComparator implements Comparator {
    public Integer compare(Object obj1, Object obj2) {
        MyClass a = (MyClass)obj1;
        MyClass b = (MyClass)obj2;
        if (a.value == b.value) return 0;
        return a.value > b.value ? 1 : -1;
    }
}

List<MyClass> myList = new List<MyClass>{ new MyClass(5), new MyClass(1), new MyClass(3) };
myList.sort(new MyComparator());

41. Can you give an example of a Comparator implementation that sorts strings in reverse alphabetical order?

  • Example:
public class ReverseStringComparator implements Comparator {
    public Integer compare(Object obj1, Object obj2) {
        String str1 = (String)obj1;
        String str2 = (String)obj2;
        return str2.compareTo(str1); // Reverse order
    }
}

// Usage
List<String> myStrings = new List<String>{'apple', 'banana', 'cherry'};
myStrings.sort(new ReverseStringComparator());

42. What are some practical use cases for implementing the Comparable interface in custom Apex classes?

Some practical use cases include:

  • Sorting records: When you need to sort custom objects based on a single field, such as sorting a list of accounts by their revenue.
  • Implementing priority queues: Where objects need to be processed in a specific order based on their priority.
  • Filtering data: When comparing objects to filter or identify unique entries.
  • Example:
public class AccountComparable implements Comparable {
    public Account acct;

    public AccountComparable(Account acct) {
        this.acct = acct;
    }

    public Integer compareTo(Object compareTo) {
        AccountComparable other = (AccountComparable)compareTo;
        if (this.acct.AnnualRevenue == other.acct.AnnualRevenue) return 0;
        return this.acct.AnnualRevenue > other.acct.AnnualRevenue ? 1 : -1;
    }
}

// Usage
List<AccountComparable> accounts = new List<AccountComparable>{
    new AccountComparable(new Account(Name='A', AnnualRevenue=50000)),
    new AccountComparable(new Account(Name='B', AnnualRevenue=150000)),
    new AccountComparable(new Account(Name='C', AnnualRevenue=250000))
};
accounts.sort();

43. How would you handle null values when implementing the Comparable or Comparator interface?

You should add null checks in the compareTo or compare methods to handle null values gracefully and avoid NullPointerException.

  • Example:
public class MyComparator implements Comparator {
    public Integer compare(Object obj1, Object obj2) {
        MyClass a = (MyClass)obj1;
        MyClass b = (MyClass)obj2;
        if (a == null && b == null) return 0;
        if (a == null) return -1;
        if (b == null) return 1;
        if (a.value == b.value) return 0;
        return a.value > b.value ? 1 : -1;
    }
}

44. Can you sort a list of objects based on multiple fields using the Comparator interface?

Yes, you can sort a list of objects based on multiple fields by implementing a custom comparator that performs multiple comparisons within the compare method.

  • Example:
public class MultiFieldComparator implements Comparator {
    public Integer compare(Object obj1, Object obj2) {
        MyClass a = (MyClass)obj1;
        MyClass b = (MyClass)obj2;
        if (a.field1 == b.field1) {
            if (a.field2 == b.field2) return 0;
            return a.field2 > b.field2 ? 1 : -1;
        }
        return a.field1 > b.field1 ? 1 : -1;
    }
}

// Usage
List<MyClass> myList = new List<MyClass>{ new MyClass(5, 10), new MyClass(1, 20), new MyClass(5, 15) };
myList.sort(new MultiFieldComparator());

Documentation: Comparator Interface Multi-Field Sorting

45. What are Governor Limits in Salesforce?

Governor Limits are runtime limits enforced by the Salesforce platform to ensure efficient use of resources and maintain multi-tenant architecture. These limits prevent any single tenant from monopolizing shared resources.

  • Common Limits:
    • SOQL Queries: 100 queries per transaction.
    • DML Statements: 150 DML operations per transaction.
    • Heap Size: 6 MB for synchronous, 12 MB for asynchronous execution.
    • CPU Time: 10,000 milliseconds.
  • Documentation: Governor Limits

46. What is SOQL? How is it different from SOSL?

  • SOQL (Salesforce Object Query Language): Used to query Salesforce database to retrieve records. It is similar to SQL.
  • SOSL (Salesforce Object Search Language): Used to search text, email, and phone fields across multiple objects.
  • Example of SOQL:
List <Account> accounts = [SELECT Id, Name FROM Account WHERE Name LIKE 'Acme%'];

Example of SOSL:

List<List<SObject>> searchList = [FIND 'Joe Smith' IN ALL FIELDS RETURNING Account(Name), Contact(FirstName, LastName)];

Documentation: SOQL and SOSL

47. What is a Batch Apex? When would you use it?

Batch Apex allows you to define a single job that can be broken up into manageable chunks that are processed separately. It is useful for processing large volumes of records without hitting governor limits.

Example:

global class AccountBatch implements Database.Batchable<SObject> {
    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }
    
    global void execute(Database.BatchableContext BC, List<Account> scope) {
        for (Account acc : scope) {
            acc.Name = acc.Name + ' - Updated';
        }
        update scope;
    }
    
    global void finish(Database.BatchableContext BC) {
        // Post processing logic
    }
}

Documentation: Batch Apex

48. How do you execute a Batch Apex job?

You execute a Batch Apex job by creating an instance of the batch class and calling the Database.executeBatch method, optionally passing in a batch size.

  • Example:
MyBatchClass batchJob = new MyBatchClass();
Database.executeBatch(batchJob, 200);

49. What are the key methods in a Batch Apex class?

A Batch Apex class must implement the Database.Batchable interface, which includes three key methods:

  • start: Initializes the batch job and returns either a Database.QueryLocator or an Iterable that specifies the scope of objects to be processed.
  • execute: Contains the main logic for processing each batch of data.
  • finish: Executes after all batches are processed and can be used for post-processing or sending notifications.
  • Example:
public class MyBatchClass implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        // Logic for start
    }

    public void execute(Database.BatchableContext context, List<SObject> scope) {
        // Logic for execute
    }

    public void finish(Database.BatchableContext context) {
        // Logic for finish
    }
}

50. How can you monitor the status of a Batch Apex job?

You can monitor the status of a Batch Apex job using the AsyncApexJob object. By querying this object, you can check the job’s status, progress, and details.

  • Example:
// Query the AsyncApexJob object
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems FROM AsyncApexJob WHERE Id = :batchJobId];
System.debug('Job Status: ' + job.Status);

51. What is the purpose of the Database.Stateful interface in Batch Apex?

The Database.Stateful interface is used to maintain state across multiple transactions in a Batch Apex job. Without implementing this interface, instance variables of the batch class would not retain their values between execute methods.

  • Example:
public class MyStatefulBatch implements Database.Batchable<SObject>, Database.Stateful {
    public Integer processedCount = 0;

    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator('SELECT Id FROM Account');
    }

    public void execute(Database.BatchableContext context, List<Account> scope) {
        processedCount += scope.size();
        // Process scope
    }

    public void finish(Database.BatchableContext context) {
        System.debug('Total processed records: ' + processedCount);
    }
}

52. Can you call a Batch Apex job from another Batch Apex job?

Yes, you can call a Batch Apex job from another Batch Apex job. However, you must be cautious of governor limits and ensure that the total number of batch executions does not exceed the limits within a single transaction.

Example

public class FirstBatchClass implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator('SELECT Id FROM Account');
    }

    public void execute(Database.BatchableContext context, List<Account> scope) {
        // Process current batch
        if (/* condition to chain */) {
            Database.executeBatch(new SecondBatchClass(), 200);
        }
    }

    public void finish(Database.BatchableContext context) {
        System.debug('First batch job finished.');
    }
}

53. What are the governor limits specific to Batch Apex?

Some key governor limits specific to Batch Apex are:

  • Number of batch executions: Up to 250,000 per 24 hours.
  • Batch size: Can range from 1 to 2,000 records, default is 200.
  • Total number of records processed: 50 million.
  • Total number of batch jobs in the queue: 100 at a time.
  • Number of active batch jobs: Up to 5 concurrently executing.
  • Documentation: Batch Apex Limits

54. How can you handle exceptions in a Batch Apex job?

Exceptions in a Batch Apex job can be handled using try-catch blocks within the execute method. It’s important to handle exceptions appropriately to prevent the batch job from failing.

  • Example:
public class MyBatchClass implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    public void execute(Database.BatchableContext context, List<Account> scope) {
        try {
            for (Account acc : scope) {
                acc.Description = 'Processed by Batch Apex';
            }
            update scope;
        } catch (Exception e) {
            System.debug('Exception: ' + e.getMessage());
            // Handle exception, e.g., logging, reprocessing logic, etc.
        }
    }

    public void finish(Database.BatchableContext context) {
        System.debug('Batch job finished.');
    }
}

55. How do you schedule a Batch Apex job?

You can schedule a Batch Apex job using the System.schedule method by implementing the Schedulable interface and calling Database.executeBatch from the execute method.

Example

public class MySchedulableBatch implements Schedulable {
    public void execute(SchedulableContext context) {
        Database.executeBatch(new MyBatchClass(), 200);
    }
}

// Schedule the batch job
String cronExp = '0 0 12 * * ?'; // Run at noon every day
System.schedule('Daily Batch Job', cronExp, new MySchedulableBatch());

56. What is the Database.BatchableContext parameter used for in Batch Apex methods?

The Database.BatchableContext parameter provides context information about the batch job and can be used within the execute and finish methods. It allows accessing the job ID and can be used to pass state between methods if the class implements Database.Stateful.

  • Example:
public class MyBatchClass implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        System.debug('Job ID: ' + context.getJobId());
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    public void execute(Database.BatchableContext context, List<Account> scope) {
        // Processing logic
    }

    public void finish(Database.BatchableContext context) {
        System.debug('Batch job finished. Job ID: ' + context.getJobId());
    }
}

Documentation: BatchableContext

57. Explain the @future annotation and when to use it.

The @future annotation denotes methods that run asynchronously. Methods with this annotation can perform long-running operations such as callouts to external services or operations that need to be deferred to reduce blocking of current execution.

Example:

public class FutureExample {
    @future
    public static void updateAccountAsync(Set<Id> accountIds) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
        for (Account acc : accounts) {
            acc.Name = acc.Name + ' - Updated';
        }
        update accounts;
    }
}

Documentation: Asynchronous Apex

58. What are Apex Sharing Reasons?

Apex Sharing Reasons allow developers to create custom reasons for sharing records in Salesforce. This enables the identification and management of why a record is shared with another user or group.

Example:

AccountShare accShare = new AccountShare();
accShare.AccountId = '001xx000003DGSWAA4';
accShare.UserOrGroupId = '005xx000001Sv6mAAC';
accShare.AccountAccessLevel = 'Read';
accShare.RowCause = Schema.AccountShareRowCause.Manual;
insert accShare;

Documentation: Apex Sharing

59. Describe the use of Custom Metadata Types in Apex.

Custom Metadata Types are used to define and manage metadata in Salesforce. They allow you to create custom sets of metadata and associate them with application logic, simplifying maintenance and reducing hardcoded values.

Example:

List<CustomMetadataType__mdt> customMetadata = [SELECT MasterLabel, DeveloperName FROM CustomMetadataType__mdt];
for (CustomMetadataType__mdt cmd : customMetadata) {
    System.debug(cmd.MasterLabel);
}

Documentation: Custom Metadata Types

60. How can you make a callout from Apex? Provide an example.

Apex HTTP callouts enable you to integrate with external web services. The Http class in Apex provides methods to make HTTP requests.

Example:

public class CalloutExample {
    public String makeGetCallout() {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://api.example.com/data');
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        return response.getBody();
    }
}

Documentation: Apex Callouts

61. What are the types of collections in Apex? Provide examples.

In Apex, collections are used to store multiple elements in a single variable. The three types of collections in Apex are Lists, Sets, and Maps.

List: An ordered collection of elements that can contain duplicates.

List<String> names = new List<String>{'John', 'Jane', 'Joe'};
names.add('Jack');
System.debug(names); // Output: (John, Jane, Joe, Jack)

Set: An unordered collection of unique elements.

Set<String> uniqueNames = new Set<String>{'John', 'Jane', 'Joe'};
uniqueNames.add('Jack');
uniqueNames.add('John'); // 'John' will not be added again
System.debug(uniqueNames); // Output: {John, Jane, Joe, Jack}

Map: A collection of key-value pairs where each key is unique.

Map<String, Integer> nameToAge = new Map<String, Integer>{'John' => 30, 'Jane' => 25};
nameToAge.put('Joe', 35);
System.debug(nameToAge); // Output: {John=30, Jane=25, Joe=35}

Documentation: Apex Collections

62. Explain the difference between Set and List in Apex.

List: An ordered collection that allows duplicate elements. It is indexed, which means you can access elements by their position.

List<String> fruits = new List<String>{'Apple', 'Banana', 'Apple'};
System.debug(fruits[0]); // Output: Apple

Set: An unordered collection that does not allow duplicate elements. Sets are used when the uniqueness of elements is required.

Set<String> fruits = new Set<String>{'Apple', 'Banana', 'Apple'};
System.debug(fruits.contains('Apple')); // Output: true
System.debug(fruits.size()); // Output: 2

Documentation: Apex Sets

63. How do you handle exceptions in Apex? Provide code examples.

Apex uses try-catch blocks to handle exceptions. You can catch specific exceptions or use a general exception handler.

Example:

try {
    // Code that may throw an exception
    Integer result = 10 / 0;
} catch (MathException e) {
    System.debug('MathException caught: ' + e.getMessage());
} catch (Exception e) {
    System.debug('Generic exception caught: ' + e.getMessage());
} finally {
    System.debug('This block is always executed');
}

Documentation: Apex Exception Handling

64. What is the use of the @InvocableMethod annotation?

The @InvocableMethod annotation is used to define methods that can be called from Flow or Process Builder. This allows declarative automation tools to execute custom Apex code.

Example:

public class AccountActions {
    @InvocableMethod
    public static void updateAccountNames(List<Id> accountIds) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
        for (Account acc : accounts) {
            acc.Name = acc.Name + ' - Updated';
        }
        update accounts;
    }
}

Documentation: InvocableMethod Annotation

65. What is the purpose of the System.assert statement in Apex testing?

System.assert is used in test classes to verify that the code behaves as expected. It compares an actual value against an expected value, and if they don’t match, an exception is thrown.

Example:

@isTest
private class AccountTest {
    @isTest static void testAccountUpdate() {
        Account acc = new Account(Name = 'Test Account');
        insert acc;
        
        // Perform some operations
        acc.Name = 'Updated Account';
        update acc;
        
        // Query and assert the updated value
        Account updatedAcc = [SELECT Name FROM Account WHERE Id = :acc.Id];
        System.assert(updatedAcc.Name == 'Updated Account', 'The account name should be updated');
    }
}

Documentation: System.assert

66. How do you write test classes in Apex?

Answer: Apex test classes are written to ensure that your code works as expected and to achieve code coverage. Test classes are annotated with @isTest.

Example:

@isTest
public class AccountServiceTest {
    @isTest static void testCreateAccount() {
        // Test data setup
        String accountName = 'Test Account';
        
        // Call the method to be tested
        AccountService.createAccount(accountName);
        
        // Verify the results
        Account acc = [SELECT Id, Name FROM Account WHERE Name = :accountName];
        System.assertNotEquals(null, acc, 'Account should be created');
    }
}

Documentation: Testing Apex

67. Explain the purpose and use of Test.startTest() and Test.stopTest().

Answer: Test.startTest() and Test.stopTest() are used to reset governor limits and to test asynchronous code.

Example:

@isTest
private class FutureMethodTest {
    @isTest static void testFutureMethod() {
        Test.startTest();
        FutureExample.updateAccountAsync(new Set<Id>{'001xx000003DGSWAA4'});
        Test.stopTest();
        
        // Verify the future method logic
        Account acc = [SELECT Name FROM Account WHERE Id = '001xx000003DGSWAA4'];
        System.assert(acc.Name.contains(' - Updated'), 'Account name should be updated');
    }
}

Documentation: Test Methods

68. What are Custom Settings? How are they different from Custom Metadata?

  • Custom Settings: Allow you to create custom data sets that your application can use for configuration and other purposes. They can be either list or hierarchy types.
  • Custom Metadata: Similar to custom settings, but records are deployable and packagable. Custom metadata types are more suited for configurations that are part of your application.

Example of Custom Settings:

CustomSetting__c setting = CustomSetting__c.getInstance('SettingName');
System.debug(setting.Field__c);

Example of Custom Metadata:

List<CustomMetadataType__mdt> metadataRecords = [SELECT MasterLabel, Field__c FROM CustomMetadataType__mdt];
for (CustomMetadataType__mdt record : metadataRecords) {
    System.debug(record.MasterLabel);
}

Documentation:

69. Describe the process of creating a Visualforce page.

Visualforce pages are used to create custom user interfaces in Salesforce. They use a tag-based markup language similar to HTML.

Steps to create a Visualforce page:

  1. Navigate to: Setup → Visualforce Pages.
  2. Click on: New.
  3. Enter a name , below markup and Save the page.
<apex:page>
    <apex:form>
        <apex:pageBlock title="Hello World">
            <apex:pageBlockSection>
                <apex:outputText value="Hello, {!$User.FirstName}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

70. How do you optimize SOQL queries in Apex?

Optimizing SOQL queries involves best practices to improve performance and avoid hitting governor limits.

  • Best Practices:
    • Use selective filters: Use indexed fields in WHERE clauses.
    • Avoid using ‘IN’ with large collections: Use query more efficiently with smaller sets.
    • Use LIMIT: Limit the number of records returned.
    • Use Relationship Queries: Reduce the number of queries by querying related records in a single query.

Example:

List<Account> accounts = [SELECT Id, Name FROM Account WHERE Industry = 'Technology' LIMIT 100];

Documentation: SOQL Best Practices

Conclusion

By going through the above article i am confident you will get a fare idea about your knowledge on salesforce apex. You can then dig deeper into the areas where needs more attention and keep practicing and prepare yourself for the interview.

The Salesforce Apex Interview Questions and Prep guide can become the ultimate tool, your wing man to perform in for that next role.

Best of Luck ! we hope this article serves its purpose to prepare you well for your upcoming interview.

If you find this log useful, do share it with your friends as well and don’t forget to subscribe to our newsletter to receive such content right into your inbox.