Apex Syntax Basics

Share

This post teaches the core Salesforce Apex language fundamentals you must know: syntax basics, classes/methods/constructors, method overloading, collections (List/Set/Map), SOQL + DML, exception handling, access modifiers, and the #1 bulk pattern (Ids → Query → Map → Apply → DML). It also includes a beginner mistake checklist and interview questions.


Salesforce Apex Language Fundamentals & Syntax

What Is Apex?

Apex is Salesforce’s strongly typed, Java-like server-side language used to build business logic, automation, and integrations on the Salesforce platform. Because Salesforce is multi-tenant, Apex must respect governor limits (SOQL, DML, CPU time, heap, etc.).

Common mistakes

  • Writing Apex like Java without thinking about limits

  • Doing SOQL/DML repeatedly inside loops


Apex Basic Syntax (Core Notations)

Apex fundamentals include types, variables, conditionals, loops, and expressions.

Integer i = 10;
Decimal amount = 25.50;
String name = 'Acme';
Boolean isActive = true;
Date d = Date.today();
Datetime dt = System.now();

if (amount > 20) {
    System.debug('High amount');
} else {
    System.debug('Low amount');
}

for (Integer idx = 0; idx < 5; idx++) { }
while (i > 0) { i--; }

Common mistakes

  • Not handling null and blank strings

  • Confusing Date vs Datetime

  • Overusing debug logs in production


Apex Classes, Methods, and Constructors

Apex code lives inside classes.

  • Methods do work and may return values

  • Constructors initialize object state

public class InvoiceService {
    private Decimal taxRate; // instance field

    public InvoiceService(Decimal taxRate) { // constructor
        this.taxRate = taxRate;
    }

    public Decimal totalWithTax(Decimal subtotal) { // instance method
        return subtotal + (subtotal * taxRate);
    }

    public static String normalizeName(String raw) { // static method
        return String.isBlank(raw) ? null : raw.trim().toUpperCase();
    }
}

Key ideas

  • No “free functions” outside classes

  • static belongs to the class; instance members belong to the object

  • this refers to the current instance

Common mistakes

  • Putting too much logic in constructors

  • Using instance state when a static utility method is better (and vice-versa)


Method Overloading in Apex

Method overloading means the same method name with different parameters.

public class MathUtil {
    public static Integer add(Integer a, Integer b) { return a + b; }
    public static Decimal add(Decimal a, Decimal b) { return a + b; }
}

Common mistakes

  • Confusing overloading with overriding

  • Creating ambiguous overloads with similar parameter types


Apex Collections: List vs Set vs Map

Collections are key to writing bulk-safe Apex.

  • List: ordered, duplicates allowed

  • Set: unique values only

  • Map: key → value lookup (fastest for searching)

List<String> names = new List<String>{ 'A', 'B' };
names.add('C');

Set<Id> ids = new Set<Id>();
ids.add('001XXXXXXXXXXXXXXX');

Map<Id, Account> acctById = new Map<Id, Account>();

Common mistakes

  • Searching lists repeatedly instead of using maps

  • Forgetting Sets remove duplicates automatically

  • Holding too much in memory (heap limit)


Map in Apex

What a Map Is (and Why It Matters)

A Map stores unique keys mapped to values. Maps are essential for bulkification because they avoid repeated scanning and nested loops.

Map<Id, Account> acctById = new Map<Id, Account>();

Why Maps Are Master-Level

Maps help you:

  • avoid SOQL in loops

  • avoid nested loops (better CPU usage)

  • “join” data efficiently (parent ↔ child)

  • build scalable triggers, batches, and APIs


Common Map Types

// 1) Id -> SObject (most common)
Map<Id, Account> acctById = new Map<Id, Account>([
    SELECT Id, Name FROM Account LIMIT 10
]);

// 2) String -> Id (external keys / json keys)
Map<String, Id> oppByApplicationId = new Map<String, Id>();

// 3) Id -> List<Child> (grouping)
Map<Id, List<Contact>> contactsByAccountId = new Map<Id, List<Contact>>();

Core Map Operations

m.put(acc.Id, acc);        // add/replace
Account a = m.get(acc.Id); // null if missing
m.containsKey(acc.Id);     // true/false

m.remove(acc.Id);

m.keySet();                // Set of keys
m.values();                // List of values
m.size();                  // count

Remember

  • put() overwrites existing value if key exists

  • get() returns null if not found

  • iteration order isn’t guaranteed


The #1 Apex Bulk Pattern (Mini Diagram)

Ids → Query once → Map → Apply → DML once

Input Records
   |
Collect IDs (Set<Id>)
   |
SOQL once (WHERE Id IN :ids)
   |
Store in Map (Map<Id, SObject>)
   |
Loop input + map.get() lookups
   |
DML once (update/insert/upsert)

Bulkification Example (Bad vs Good)

Bad (nested loops)

for (Contact c : contacts) {
    for (Account a : accounts) {
        if (c.AccountId == a.Id) c.Description = a.Name;
    }
}

Good (Map lookup)

Map<Id, Account> acctById = new Map<Id, Account>(accounts);

for (Contact c : contacts) {
    Account a = acctById.get(c.AccountId);
    if (a != null) c.Description = a.Name;
}

Standard Trigger Shape (Bulk-Safe)

Set<Id> acctIds = new Set<Id>();
for (Contact c : Trigger.new) {
    if (c.AccountId != null) acctIds.add(c.AccountId);
}

Map<Id, Account> acctById = new Map<Id, Account>([
    SELECT Id, Industry
    FROM Account
    WHERE Id IN :acctIds
]);

for (Contact c : Trigger.new) {
    Account a = acctById.get(c.AccountId);
    if (a != null) c.Department = a.Industry;
}

Grouping Pattern: Map<Id, List<Child>>

Map<Id, List<Contact>> byAcct = new Map<Id, List<Contact>>();

for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId != null]) {
    if (!byAcct.containsKey(c.AccountId)) {
        byAcct.put(c.AccountId, new List<Contact>());
    }
    byAcct.get(c.AccountId).add(c);
}

Relationship Query Shortcut (No Manual Grouping)

List<Account> accts = [
    SELECT Id, Name, (SELECT Id, Email FROM Contacts)
    FROM Account
    WHERE Id IN :acctIds
];

Map Pitfalls Checklist

  • Null-check map.get(key)

  • Don’t assume iteration order

  • Watch heap usage for large maps

  • Prefer maps for lookups instead of scanning lists


sObjects, SOQL, and DML

SOQL reads data; DML writes data.

Account a = new Account(Name = 'Acme');
insert a;

List<Account> accts = [
    SELECT Id, Name
    FROM Account
    WHERE Name LIKE 'Ac%'
    LIMIT 50
];

for (Account acc : accts) acc.Name += ' - Updated';
update accts;

delete accts;

Partial Success DML

Database.SaveResult[] results = Database.insert(accts, false); // allOrNone = false

Common mistakes

  • SOQL/DML inside loops

  • Querying too many rows/fields

  • Not handling partial failures when needed


Exceptions and Error Handling

Use try/catch/finally to handle failures safely.

try {
    insert new Account(); // missing Name -> DmlException
} catch (DmlException e) {
    System.debug('DML failed: ' + e.getMessage());
} catch (Exception e) {
    System.debug('Unexpected: ' + e.getMessage());
} finally {
    System.debug('Always runs');
}

Common mistakes

  • Catching only Exception and hiding details

  • Swallowing exceptions without any logging/handling


Access Modifiers (Visibility)

Use modifiers to control access:

  • private: only inside the class

  • protected: class + subclasses

  • public: available in the org

  • global: needed for managed packages / broad exposure

Also common:

  • static: shared per transaction

  • final: cannot be changed or overridden

Common mistakes

  • Making everything public

  • Using global when not required


Key Apex Keywords to Know

  • extends: Inheritance — a class inherits fields/methods from a parent class.

  • implements: Interface contract — a class must provide implementations for all interface methods.

  • virtual: Allows a class/method to be overridden in a child class (Apex requires virtual or abstract to override).

  • override: Marks a method that replaces a parent class’s virtual/abstract method implementation.

  • with sharing: Enforces record-level sharing rules (who can see which records) for that class.

  • without sharing: Ignores sharing rules (runs in system context for sharing). Still doesn’t automatically bypass CRUD/FLS.

  • throw: Manually raises an exception to stop normal execution and signal an error.

  • try / catch / finally: Error handling — try runs code, catch handles exceptions, finally runs always (cleanup/logging).

  • new: Creates a new instance (object) like new Account(...) or new MyClass().

  • return: Sends a value back from a method (or exits the method early).


Mini End-to-End Demo (Real Apex Shape)

Validate → build list → DML → handle error.

public with sharing class Demo {
    public static List<Account> createAccounts(List<String> names) {
        List<Account> toInsert = new List<Account>();

        for (String n : names) {
            if (String.isBlank(n)) continue;
            toInsert.add(new Account(Name = n.trim()));
        }

        try {
            insert toInsert;
        } catch (DmlException e) {
            throw new AuraHandledException('Could not insert accounts: ' + e.getMessage());
        }

        return toInsert;
    }
}

FAQ / Interview Questions

  1. What’s the difference between static and instance members in Apex?

  2. Why are Maps critical in trigger code? Show the bulk pattern.

  3. What happens if you run SOQL or DML inside a loop?

  4. insert list vs Database.insert(list, false) — what’s the difference?

  5. When do you use with sharing vs without sharing?

  6. Method overloading vs overriding — how are they different?

  7. What’s your strategy for handling DML errors in bulk operations?


Conclusion

If you master these fundamentals—especially collections and the bulk pattern (Ids → Query → Map → Apply → DML)—you’ll write Apex that is faster, safer, and production-ready.

  • October 11, 2025