Apex Programming — A Complete Deep Dive to Crack Your Salesforce Interview

Published On: April 5, 2026

If you’re preparing for a Salesforce developer interview, Apex is not just another topic—it’s the core language that proves you understand how the platform truly works. Many candidates learn syntax, but interviews are rarely about syntax alone. They’re about how you think, how you design, and how well you respect the platform’s limits.

This guide is designed to take you beyond memorization and into real understanding—so you can walk into your interview with clarity, confidence, and control.

Salesforce Apex

Apex Programming — Complete Deep Dive

Every concept explained in detail: data types, OOP, SOQL, DML, collections, exception handling, async Apex, testing, governor limits, and scenario-based interview questions with full code.

Data Types & Variables
OOP Concepts
SOQL & DML
Collections
Exception Handling
Async Apex
Testing
Governor Limits
Interview Q&A
Scenarios
Data Types & OOP
SOQL & DML
Collections
Exceptions
Async Apex
Testing
Governor Limits
Interview Q&A
Scenarios
Data types, variables & OOP in Apex

Apex is a strongly typed, object-oriented language that runs on the Salesforce platform. Understanding primitive types, collections, and OOP concepts is the foundation of every Apex developer interview.

Integer
32-bit whole number
Range: -2,147,483,648 to 2,147,483,647. Default: null. Use for counts, IDs.
Long
64-bit whole number
For very large numbers. Suffix L: 123456789L. Wider than Integer.
Decimal
High-precision decimal
Used for currency, percentages. More precise than Double. 38 significant digits.
Double
64-bit float
Scientific notation. Less precise than Decimal. Use for math calculations.
String
Text value
Max 255 chars for fields, unlimited in code. Null-safe with String.isBlank().
Boolean
true / false / null
Three states in Apex — null is distinct from false. Always check for null.
Date / DateTime
Date and timestamp
Date.today(), DateTime.now(). Use .format() for display strings.
Id
Salesforce record Id
15 or 18 char. Case-sensitive in comparisons. Always use 18-char in code.
Apex data types — complete reference with examples
// ── Primitive types ───────────────────────────────────────────
Integer  count    = 42;
Long     bigNum   = 9876543210L;
Double   pi       = 3.14159;
Decimal  revenue  = 99999.99;
String   name     = 'Salesforce';
Boolean  isActive = true;
Date     today    = Date.today();
DateTime now      = DateTime.now();
Id       recordId = '001xx000003GYkl';

// ── String utility methods ────────────────────────────────────
String s = '  Hello World  ';
s.trim();                          // 'Hello World'
s.toLowerCase();                   // '  hello world  '
s.contains('World');               // true
s.startsWith('Hello');             // false (has leading space)
s.replace('World', 'Apex');       // '  Hello Apex  '
s.split(' ');                      // List of words
String.isBlank(s);                  // false
String.isEmpty('');               // true
String.valueOf(123);               // '123' — convert Integer to String
Integer.valueOf('42');             // 42 — parse String to Integer

// ── Date/DateTime operations ──────────────────────────────────
Date d = Date.today();
d.addDays(7);           // 7 days from now
d.addMonths(1);         // next month
d.daysBetween(Date.today().addDays(30)); // 30
d.format();             // 'M/d/yyyy' locale format

// ── Type casting ─────────────────────────────────────────────
Object obj = 'hello';
String str = (String) obj;    // explicit cast
if (obj instanceof String) { // safe type check before cast
    str = (String) obj;
}
Object-oriented programming in Apex

Apex supports all four OOP pillars: Encapsulation, Inheritance, Polymorphism, and Abstraction. These are tested heavily in senior Salesforce developer interviews.

OOP — Classes, Inheritance, Interfaces, Abstract — complete
// ── Encapsulation: access modifiers ──────────────────────────
public class BankAccount {
    private Decimal balance;          // hidden from outside
    public  String  accountNumber;     // accessible from outside
    protected String ownerName;        // accessible in subclasses
    global  String  bankName;          // accessible across namespaces

    public BankAccount(Decimal initialBalance) {
        this.balance = initialBalance;
    }

    public Decimal getBalance() { return this.balance; }

    public void deposit(Decimal amount) {
        if (amount <= 0) throw new IllegalArgumentException('Amount must be positive');
        this.balance += amount;
    }
}

// ── Inheritance: extends ──────────────────────────────────────
public virtual class Animal {
    public String name;

    public Animal(String name) { this.name = name; }

    public virtual String speak() { return '...'; }

    public String describe() {
        return name + ' says: ' + speak();
    }
}

public class Dog extends Animal {
    public Dog(String name) { super(name); }

    public override String speak() { return 'Woof!'; }
}

public class Cat extends Animal {
    public Cat(String name) { super(name); }

    public override String speak() { return 'Meow!'; }
}

// Polymorphism in action:
List<Animal> animals = new List<Animal>{
    new Dog('Rex'), new Cat('Luna')
};
for (Animal a : animals) {
    System.debug(a.describe()); // Rex says: Woof! / Luna says: Meow!
}

// ── Abstract class — cannot be instantiated ───────────────────
public abstract class Shape {
    public String color;

    public abstract Double area();      // subclasses MUST implement
    public abstract Double perimeter();

    public String describe() {         // concrete method in abstract class
        return color + ' shape: area=' + area() + ' perimeter=' + perimeter();
    }
}

public class Circle extends Shape {
    private Double radius;
    public Circle(Double r) { this.radius = r; color = 'Red'; }
    public override Double area()      { return Math.PI * radius * radius; }
    public override Double perimeter() { return 2 * Math.PI * radius; }
}

// ── Interface — contract that any class can implement ─────────
public interface Discountable {
    Decimal applyDiscount(Decimal price);
    String  getDiscountType();
}

public interface Taxable {
    Decimal calculateTax(Decimal price);
}

// Class implementing multiple interfaces
public class PremiumProduct implements Discountable, Taxable {
    public Decimal applyDiscount(Decimal price) { return price * 0.85; }
    public String  getDiscountType()                { return '15% Premium'; }
    public Decimal calculateTax(Decimal price)    { return price * 0.18; }
}

// ── Static vs Instance members ────────────────────────────────
public class MathHelper {
    public static Integer callCount = 0;  // shared across all instances

    public static Integer square(Integer n) {
        callCount++;
        return n * n;
    }
    public static Double sqrt(Double n) { return Math.sqrt(n); }
}

// Usage: no instantiation needed
Integer result = MathHelper.square(5); // 25

// ── Inner classes ─────────────────────────────────────────────
public class OuterClass {
    private String secret = 'hidden';

    public class InnerClass {        // inner class — can access outer members
        public String getSecret() { return 'no direct access in Apex'; }
    }
}
SOQL & DML — complete reference

Salesforce Object Query Language (SOQL) is used to read data. DML statements (insert, update, delete, upsert, merge, undelete) are used to write data. Mastering both is essential for every Apex developer.

SOQL — every clause and pattern explained
// ── Basic SELECT ──────────────────────────────────────────────
List<Account> accounts = [
    SELECT Id, Name, Industry, AnnualRevenue, Phone
    FROM Account
    WHERE Industry = 'Technology'
      AND AnnualRevenue > 1000000
    ORDER BY Name ASC
    LIMIT 100
];

// ── Bind variables (prevent SOQL injection) ───────────────────
String industry = 'Technology';
Decimal minRevenue = 500000;
List<Account> filtered = [
    SELECT Id, Name FROM Account
    WHERE Industry = :industry
      AND AnnualRevenue >= :minRevenue
];

// ── LIKE operator (wildcard search) ───────────────────────────
String key = '%Salesforce%';
List<Account> matched = [SELECT Id, Name FROM Account WHERE Name LIKE :key];

// ── IN operator with collections ──────────────────────────────
Set<String> industries = new Set<String>{'Technology', 'Finance'};
List<Account> byIndustry = [
    SELECT Id, Name FROM Account WHERE Industry IN :industries
];

// ── Relationship queries ───────────────────────────────────────
// Child-to-parent (dot notation)
List<Contact> contacts = [
    SELECT Id, FirstName, LastName, Account.Name, Account.Industry
    FROM Contact
    WHERE Account.Industry = 'Technology'
];

// Parent-to-child (subquery)
List<Account> withContacts = [
    SELECT Id, Name,
        (SELECT Id, FirstName, LastName, Email
         FROM Contacts
         ORDER BY LastName)
    FROM Account
    WHERE Id IN :accountIds
];
for (Account acc : withContacts) {
    for (Contact c : acc.Contacts) {
        System.debug(c.FirstName + ' works at ' + acc.Name);
    }
}

// ── Aggregate functions ───────────────────────────────────────
AggregateResult[] results = [
    SELECT Industry,
           COUNT(Id) total,
           SUM(AnnualRevenue) totalRevenue,
           AVG(AnnualRevenue) avgRevenue,
           MAX(AnnualRevenue) maxRevenue,
           MIN(AnnualRevenue) minRevenue
    FROM Account
    WHERE AnnualRevenue != null
    GROUP BY Industry
    HAVING COUNT(Id) > 5
    ORDER BY COUNT(Id) DESC
];
for (AggregateResult ar : results) {
    String  ind   = (String)  ar.get('Industry');
    Integer total = (Integer) ar.get('total');
    Decimal sum   = (Decimal) ar.get('totalRevenue');
}

// ── NULL handling ─────────────────────────────────────────────
[SELECT Id FROM Account WHERE Phone  = null]   // no phone set
[SELECT Id FROM Account WHERE Phone != null]   // has phone

// ── FOR UPDATE (row locking) ──────────────────────────────────
List<Account> locked = [SELECT Id, Name FROM Account WHERE Id = :accId FOR UPDATE];

// ── Dynamic SOQL (Database.query) ────────────────────────────
String query = 'SELECT Id, Name FROM Account WHERE Industry = \'' + String.escapeSingleQuotes(industry) + '\' LIMIT 50';
List<Account> dynamic = Database.query(query);
DML — all operations with Database class and error handling
// ── Direct DML — all or nothing (throws on any failure) ───────
Account acc = new Account(Name = 'Acme', Phone = '555-1234');
insert acc;                    // Id is populated after insert

acc.AnnualRevenue = 500000;
update acc;

delete acc;
undelete acc;                  // restores from recycle bin

// ── Upsert — insert if not exists, update if exists ──────────
Account upsertAcc = new Account(
    External_Id__c = 'EXT-001',
    Name = 'External Account'
);
upsert upsertAcc External_Id__c;  // match on external ID field

// ── Database class — partial success (doesn't throw) ─────────
List<Account> accs = new List<Account>{
    new Account(Name = 'Valid Account'),
    new Account()  // missing required Name — will fail
};

List<Database.SaveResult> results = Database.insert(accs, false); // allOrNone=false

for (Integer i = 0; i < results.size(); i++) {
    Database.SaveResult sr = results[i];
    if (sr.isSuccess()) {
        System.debug('Inserted: ' + sr.getId());
    } else {
        for (Database.Error err : sr.getErrors()) {
            System.debug('Error on record ' + i + ': ' + err.getMessage());
            System.debug('Fields: ' + err.getFields());
        }
    }
}

// ── Database.UpsertResult ─────────────────────────────────────
Database.UpsertResult ur = Database.upsert(upsertAcc, External_Id__c, false);
if (ur.isSuccess()) {
    if (ur.isCreated()) System.debug('New record: ' + ur.getId());
    else                System.debug('Updated: '    + ur.getId());
}

// ── Mixed DML error — avoid combining setup and non-setup DML ─
// WRONG: User (setup object) + Account (non-setup) in same context
// insert user; insert account; // throws MixedDmlException
// CORRECT: Use @future or System.runAs() in tests
@future
public static void insertUserAsync(String username) {
    User u = new User(Username = username);
    insert u;
}
Collections — List, Set, Map in depth

Collections are the backbone of bulkified Apex. Mastering List, Set, and Map — including their methods, use cases, and performance characteristics — is mandatory for production-quality code.

List — ordered, allows duplicates, index access
// ── List declaration and initialization ───────────────────────
List<String>  names  = new List<String>();
List<Integer> nums   = new List<Integer>{1, 2, 3, 4, 5};
List<Account> accs   = [SELECT Id, Name FROM Account LIMIT 10];

// ── Core methods ──────────────────────────────────────────────
names.add('Alice');              // append to end
names.add(0, 'Bob');             // insert at index 0
names.addAll(new List<String>{'Charlie', 'Diana'}); // add multiple
names.remove(0);                 // remove by index
names.set(0, 'Eve');              // replace at index
String first = names.get(0);     // get by index — O(1)
Integer size = names.size();     // count
Boolean has  = names.contains('Alice'); // O(n) — use Set for membership
names.sort();                     // ascending sort in place
names.clear();                    // remove all

// ── List of SObjects — critical for DML ───────────────────────
List<Account> toUpdate = new List<Account>();
for (Account acc : [SELECT Id, Rating FROM Account]) {
    if (acc.Rating == null) {
        acc.Rating = 'Cold';
        toUpdate.add(acc);
    }
}
if (!toUpdate.isEmpty()) update toUpdate; // 1 DML

// ── Sorting with Comparable interface ────────────────────────
public class Employee implements Comparable {
    public String  name;
    public Integer salary;
    public Integer compareTo(Object other) {
        Employee e = (Employee) other;
        return this.salary - e.salary; // ascending by salary
    }
}
List<Employee> employees = ...; 
employees.sort(); // uses compareTo()
Set and Map — complete guide with performance notes
// ── Set — unordered, NO duplicates, O(1) membership check ─────
Set<String> unique = new Set<String>{'a', 'b', 'c'};
unique.add('a');              // no duplicate added — size stays 3
unique.addAll(new List<String>{'d', 'e'});
Boolean has = unique.contains('b'); // O(1) — much faster than List.contains()
unique.remove('a');
Set<String> copy = unique.clone();

// Convert Set ↔ List
List<String> fromSet = new List<String>(unique);
Set<String>  fromList = new Set<String>(fromSet);

// ── Best practice: collect IDs into Set for SOQL ─────────────
Set<Id> accountIds = new Set<Id>();
for (Contact c : Trigger.new) {
    if (c.AccountId != null) accountIds.add(c.AccountId);
}
Map<Id, Account> accMap = new Map<Id, Account>(
    [SELECT Id, Name FROM Account WHERE Id IN :accountIds]
);

// ── Map — key-value pairs, O(1) lookup by key ─────────────────
Map<String, Integer> scores = new Map<String, Integer>{
    'Alice' => 95,
    'Bob'   => 87,
    'Carol' => 92
};

scores.put('Dave', 78);         // add/replace entry
scores.putAll(anotherMap);       // merge maps
Integer alice = scores.get('Alice');  // 95 — O(1)
Boolean hasKey = scores.containsKey('Dave'); // true
scores.remove('Bob');
Set<String> keys   = scores.keySet();   // all keys
List<Integer> vals = scores.values();   // all values
Integer sz        = scores.size();

// ── Map<Id, SObject> — most common pattern ────────────────────
Map<Id, Account> accountMap = new Map<Id, Account>(
    [SELECT Id, Name, Industry FROM Account]
); // constructor from List automatically maps by Id

// O(1) lookup — much better than looping through List
Account found = accountMap.get(someId);

// ── Map of Lists — grouping pattern ──────────────────────────
Map<String, List<Contact>> byLastName = new Map<String, List<Contact>>();
for (Contact c : contacts) {
    if (!byLastName.containsKey(c.LastName)) {
        byLastName.put(c.LastName, new List<Contact>());
    }
    byLastName.get(c.LastName).add(c);
}
Exception handling — complete guide

Proper exception handling prevents data corruption, provides meaningful error messages to users, and enables graceful recovery. Understanding exception types and patterns is critical for production Apex.

DmlException
Thrown when DML fails (required field missing, validation rule, sharing violation).
QueryException
Thrown when SOQL returns 0 or 2+ rows for a single-record query like [SELECT…] without LIMIT.
NullPointerException
Accessing a property or method on a null object reference.
LimitException
A governor limit was exceeded. Cannot be caught — causes immediate transaction abort.
AuraHandledException
Special exception for LWC/Aura — shows a user-friendly message in the UI component.
CalloutException
HTTP callout failed — timeout, connection refused, or SSL error.
Exception handling — all patterns, custom exceptions, best practices
// ── try-catch-finally ─────────────────────────────────────────
try {
    Account acc = [SELECT Id FROM Account WHERE Name = 'NoSuchAccount'];
    update acc;
} catch (QueryException e) {
    // Query returned 0 or 2+ rows for single-record assignment
    System.debug('Query error: ' + e.getMessage());
    System.debug('Line: '        + e.getLineNumber());
    System.debug('Stack: '       + e.getStackTraceString());
} catch (DmlException e) {
    // DML failed
    for (Integer i = 0; i < e.getNumDml(); i++) {
        System.debug('DML error: '   + e.getDmlMessage(i));
        System.debug('Status code: '  + e.getDmlStatusCode(i));
        System.debug('Field names: '  + e.getDmlFieldNames(i));
    }
} catch (Exception e) {
    // Catch-all — catches any exception type not caught above
    System.debug('Unexpected: ' + e.getTypeName() + ': ' + e.getMessage());
} finally {
    // Always runs — even if exception was thrown
    // Use for: cleanup, logging, resetting flags
    System.debug('Operation complete');
}

// ── Custom exception classes ──────────────────────────────────
public class AccountServiceException extends Exception {}
public class ValidationException     extends Exception {}
public class IntegrationException     extends Exception {}

// Throwing with message
throw new AccountServiceException('Account not found for Id: ' + recordId);

// Throwing with cause (wrapping another exception)
try {
    insert acc;
} catch (DmlException e) {
    throw new AccountServiceException('Failed to create account: ' + e.getMessage(), e);
}

// ── AuraHandledException — for LWC/Aura error messages ───────
@AuraEnabled
public static Account getAccount(Id recordId) {
    try {
        return [SELECT Id, Name FROM Account WHERE Id = :recordId LIMIT 1];
    } catch (Exception e) {
        throw new AuraHandledException('Could not load account: ' + e.getMessage());
    }
}

// ── Exception hierarchy ───────────────────────────────────────
// Exception (base)
//   ├── DmlException
//   ├── QueryException
//   ├── NullPointerException
//   ├── MathException
//   ├── ListException (index out of bounds)
//   ├── TypeException  (invalid cast)
//   ├── CalloutException
//   ├── JSONException
//   ├── AuraHandledException
//   └── [YourCustomException] extends Exception

// ── Savepoint and rollback ────────────────────────────────────
Savepoint sp = Database.setSavepoint();
try {
    insert accountList;
    insert contactList;
} catch (Exception e) {
    Database.rollback(sp);   // undo all DML since setSavepoint()
    throw e;                 // re-throw for caller to handle
}
Asynchronous Apex — @future, Queueable, Batch, Scheduled

Async Apex runs in a separate transaction with higher governor limits. Choosing the right async mechanism is one of the most tested senior developer topics.

TypeUse WhenGovernor LimitsKey Feature
@futureSimple async, callouts from triggersHigher than syncCannot be chained, primitive params only
QueueableComplex async, callouts, chainingHigher than syncChain jobs, pass SObject params
BatchProcess millions of records50M rows querystart/execute/finish, Database.Batchable
ScheduledRun at specific time/recurringSync limitsCron expression, Schedulable interface
All async patterns — complete implementations
// ── @future — simplest async, primitive params only ───────────
public class FutureExample {

    // callout=true allows HTTP callouts from trigger context
    @future(callout=true)
    public static void sendToExternalSystem(
        List<Id> recordIds  // must be primitives or List/Set of primitives
    ) {
        List<Account> accounts = [
            SELECT Id, Name FROM Account WHERE Id IN :recordIds
        ];
        // HTTP callout logic here
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:My_Named_Credential/api/sync');
        req.setMethod('POST');
        req.setBody(JSON.serialize(accounts));
        HttpResponse res = h.send(req);
        if (res.getStatusCode() != 200) {
            System.debug('Callout failed: ' + res.getStatus());
        }
    }
}

// ── Queueable — recommended over @future for new code ─────────
public class AccountSyncQueueable implements Queueable, Database.AllowsCallouts {

    private final List<Account> accounts;
    private final Integer batchNumber;

    public AccountSyncQueueable(List<Account> accs, Integer batch) {
        this.accounts    = accs;
        this.batchNumber = batch;
    }

    public void execute(QueueableContext ctx) {
        try {
            // Process accounts, make callout
            Http h = new Http();
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:ERP/accounts');
            req.setMethod('POST');
            req.setBody(JSON.serialize(accounts));
            HttpResponse res = h.send(req);

            // Chain: enqueue next job if more data exists
            if (batchNumber < 10) {
                System.enqueueJob(
                    new AccountSyncQueueable(nextBatch, batchNumber + 1)
                );
            }
        } catch (Exception e) {
            System.debug('Queueable error: ' + e.getMessage());
        }
    }
}
// Usage:
Id jobId = System.enqueueJob(new AccountSyncQueueable(accounts, 1));

// ── Batch Apex — for processing millions of records ───────────
public class AccountRatingBatch
    implements Database.Batchable<SObject>, Database.Stateful {

    private Integer processedCount = 0;  // Database.Stateful — persist state
    private List<String> errors = new List<String>();

    // start(): defines the scope (query or Iterable)
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator([
            SELECT Id, AnnualRevenue, Rating
            FROM Account
            WHERE Rating = null
        ]); // can query up to 50M rows
    }

    // execute(): called once per batch (default 200 records)
    public void execute(Database.BatchableContext bc, List<Account> scope) {
        List<Account> toUpdate = new List<Account>();
        for (Account acc : scope) {
            acc.Rating = acc.AnnualRevenue > 1000000 ? 'Hot' : 'Cold';
            toUpdate.add(acc);
            processedCount++;
        }
        List<Database.SaveResult> results = Database.update(toUpdate, false);
        for (Database.SaveResult sr : results) {
            if (!sr.isSuccess()) {
                errors.add(sr.getErrors()[0].getMessage());
            }
        }
    }

    // finish(): called once after all batches complete
    public void finish(Database.BatchableContext bc) {
        AsyncApexJob job = [
            SELECT Status, JobItemsProcessed, TotalJobItems, NumberOfErrors
            FROM AsyncApexJob
            WHERE Id = :bc.getJobId()
        ];
        // Send completion email with stats
        if (!errors.isEmpty()) {
            System.debug('Errors: ' + errors);
        }
        System.debug('Processed: ' + processedCount + ' accounts');
    }
}
// Execute batch:
Id batchJobId = Database.executeBatch(new AccountRatingBatch(), 200);

// ── Scheduled Apex — run at specific times ────────────────────
public class DailyAccountCleanup implements Schedulable {
    public void execute(SchedulableContext ctx) {
        // Schedule batch from scheduled job
        Database.executeBatch(new AccountRatingBatch(), 200);
    }
}
// Schedule via Apex (cron: seconds minutes hours day month weekday year)
String cronExpr = '0 0 2 * * ?'; // every day at 2:00 AM
System.schedule('Daily Account Cleanup', cronExpr, new DailyAccountCleanup());
Apex test classes — complete guide

Salesforce requires 75% code coverage to deploy. Great test classes test behavior, edge cases, and bulk scenarios — not just coverage. These are the exact patterns used in production orgs.

Complete test class with all best practices
@IsTest
public class AccountServiceTest {

    /**
     * @TestSetup — runs once before ALL test methods in this class.
     * Data is rolled back after each test but @TestSetup data is
     * re-inserted fresh before each test method runs.
     * Use for: shared master data (Record Types, Users, base Accounts)
     */
    @TestSetup
    static void makeData() {
        // Create test accounts
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < 200; i++) {
            accounts.add(new Account(
                Name           = 'Test Account ' + i,
                Industry       = (i < 100) ? 'Technology' : 'Finance',
                AnnualRevenue  = i * 10000,
                Phone          = '555-0' + String.valueOf(i).leftPad(3, '0')
            ));
        }
        insert accounts;
    }

    // ── Test positive path ────────────────────────────────────────
    @IsTest
    static void testGetAccountsByIndustry_Technology_returnsCorrectCount() {
        List<Account> accounts = [SELECT Id FROM Account];
        System.assertEquals(200, accounts.size(), 'Should have 200 accounts');

        Test.startTest();
            List<Account> result = AccountService.getByIndustry('Technology');
        Test.stopTest();

        System.assertEquals(100, result.size(),
            'Should return exactly 100 Technology accounts');
        for (Account a : result) {
            System.assertEquals('Technology', a.Industry,
                'All results must be Technology industry');
        }
    }

    // ── Test bulk scenario (200 records) ─────────────────────────
    @IsTest
    static void testUpdateRatings_bulk200Records_noLimitException() {
        List<Account> accounts = [SELECT Id, AnnualRevenue FROM Account];

        Test.startTest();
            AccountService.updateRatings(accounts);
        Test.stopTest();

        List<Account> updated = [SELECT Id, Rating FROM Account];
        for (Account a : updated) {
            System.assertNotEquals(null, a.Rating, 'Rating should be set');
        }
    }

    // ── Test negative/exception path ──────────────────────────────
    @IsTest
    static void testSaveAccount_nullName_throwsException() {
        Account badAcc = new Account(); // missing required Name
        Boolean exceptionThrown = false;

        Test.startTest();
        try {
            AccountService.saveAccount(badAcc);
        } catch (AccountService.AccountServiceException e) {
            exceptionThrown = true;
            System.assert(e.getMessage().contains('Name'),
                'Error message should mention Name field');
        }
        Test.stopTest();

        System.assert(exceptionThrown, 'Expected exception was not thrown');
    }

    // ── Test with Mock HTTP Callout ───────────────────────────────
    @IsTest
    static void testCallout_successResponse_updatesRecord() {
        // Set up mock before Test.startTest()
        Test.setMock(HttpCalloutMock.class, new MockHttpSuccess());

        Test.startTest();
            AccountSyncQueueable.syncAccounts([SELECT Id FROM Account LIMIT 1]);
        Test.stopTest();

        Account a = [SELECT Sync_Status__c FROM Account LIMIT 1];
        System.assertEquals('Synced', a.Sync_Status__c);
    }

    // ── Mock HTTP class ───────────────────────────────────────────
    private class MockHttpSuccess implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest req) {
            HttpResponse res = new HttpResponse();
            res.setStatusCode(200);
            res.setBody('{"status":"ok"}');
            res.setHeader('Content-Type', 'application/json');
            return res;
        }
    }

    // ── Test with System.runAs() — user context testing ───────────
    @IsTest
    static void testWithStandardUser_noAdminAccess() {
        User stdUser = TestDataFactory.createStandardUser();

        System.runAs(stdUser) {
            Test.startTest();
            try {
                AccountService.adminOnlyMethod();
                System.assert(false, 'Should have thrown security exception');
            } catch (SecurityException e) {
                System.assert(true);
            }
            Test.stopTest();
        }
    }
}
Governor limits — complete reference

Governor limits are Salesforce’s multi-tenant safeguards. Every Apex developer must know the key limits and design patterns to avoid hitting them.

LimitSync (per transaction)Async (@future/Batch/Queue)Check with
SOQL queries100200Limits.getQueries()
SOQL rows returned50,00050,000Limits.getQueryRows()
DML statements150150Limits.getDMLStatements()
DML rows10,00010,000Limits.getDMLRows()
CPU time10,000ms60,000msLimits.getCpuTime()
Heap size6MB12MBLimits.getHeapSize()
Callouts100100Limits.getCallouts()
Email invocations1010Limits.getEmailInvocations()
Future calls500 (cannot nest)Limits.getFutureCalls()
Queueable jobs enqueued501 (chaining)Limits.getQueueableJobs()
Limits class — proactive limit checking pattern
// ── Limits class — check remaining limits at runtime ─────────
System.debug('SOQL used: '    + Limits.getQueries()        + '/' + Limits.getLimitQueries());
System.debug('DML used: '     + Limits.getDMLStatements()  + '/' + Limits.getLimitDMLStatements());
System.debug('CPU used: '     + Limits.getCpuTime()         + '/' + Limits.getLimitCpuTime() + 'ms');
System.debug('Heap used: '    + Limits.getHeapSize()        + '/' + Limits.getLimitHeapSize() + ' bytes');
System.debug('SOQL rows: '   + Limits.getQueryRows()       + '/' + Limits.getLimitQueryRows());

// ── Guard pattern: abort before hitting limit ─────────────────
public static void processWithGuard(List<Account> batch) {
    if (Limits.getDMLStatements() >= Limits.getLimitDMLStatements() - 5) {
        System.debug('WARNING: Approaching DML limit, offloading to async');
        System.enqueueJob(new AccountSyncQueueable(batch, 1));
        return;
    }
    update batch;
}

// ── SOQL for loops — process large datasets without heap issues
for (List<Account> batch : [
    SELECT Id, Rating FROM Account WHERE Rating = null
]) {
    for (Account acc : batch) {
        acc.Rating = 'Cold';
    }
    update batch; // update per inner batch (200 records)
    // Objects garbage collected after each batch — prevents heap overflow
}
LimitException cannot be caught. It terminates the transaction immediately. The only defense is proactive design: bulkify your code, avoid SOQL/DML in loops, and use Limits class checks to detect approaching limits before they are hit.
Interview questions & detailed answers

Tap any question to reveal the complete answer. Covers fundamentals through senior-level Apex topics.

Scenario-based coding questions

Real-world scenarios from Salesforce Apex developer interviews. Each includes the complete solution with full code, comments, and test class.

Best of Luck


Trusted by 2000+ learners to crack interviews at TCS, Infosys, Wipro, EY, and more.

Want more Real Salesforce Interview Q&As?

For All Job Seekers – 500+ Questions from Top Tech Companies → https://trailheadtitanshub.com/500-real-interview-questions-answers-from-top-tech-companies-ey-infosys-tcs-dell-salesforce-more/

Mega Interview Packs:

 Career Boosters:

Visit us On www.trailheadtitanshub.com

TrailheadTitans

At TrailheadTitans.com, we are dedicated to paving the way for both freshers and experienced professionals in the dynamic world of Salesforce. Founded by Abhishek Kumar Singh, a seasoned professional with a rich background in various IT companies, our platform aims to be the go-to destination for job seekers seeking the latest opportunities and valuable resources.

Related Post

Interview Q & A

Apex Programming — A Complete Deep Dive to Crack Your Salesforce Interview

By TrailheadTitans
|
April 5, 2026
Interview Q & A

Struggling with LWC? Here’s the Only Guide You Need

By TrailheadTitans
|
March 29, 2026
Interview Q & A

Everything You Need to Know About Apex Triggers in Salesforce

By TrailheadTitans
|
March 21, 2026
Interview Q & A

If You Know These 20 LWC Questions, Your Next Salesforce Interview Will Feel Easy

By TrailheadTitans
|
March 14, 2026

Leave a Comment