Salesforce Apex Interview Q&A Guide
Salesforce Apex Interview Q&A Guide
Apex Part 1
Section 1: Apex Fundamentals & Advanced Concepts
Data Types, Variables, Collections (Advanced)
1. Question: Explain the memory allocation differences between primitive data types and
object types in Apex. How does this impact performance and governor limits, especially
within loops involving large collections?
TS
Answer: Primitive data types (Integer, Double, Boolean, String, etc.) store their actual
values directly in the memory allocated to the variable. When assigned or passed to
A S
methods, their value is copied. Object types (sObjects, custom class instances, collections
like List, Set, Map) store a reference (memory address) to the actual object data stored
C
elsewhere in the heap memory. When assigned or passed, only the reference is copied, not
the entire object data.
C E
Impact: Copying primitives is cheap. Copying object references is also cheap, but operations
OR
on the objects they reference can be expensive.
Creating many objects within a loop (e.g., new Account() inside a for loop iterating
S F
10,000 times) consumes significant heap memory (governor limit: 6MB sync, 12MB async).
Passing large collections by reference is efficient, but modifying the collection within a
E
method affects the original collection, which can be unexpected if not handled carefully.
L
S A
Accessing object fields via references involves pointer dereferencing, which is slightly slower
than accessing primitive values directly, though usually negligible unless done excessively in
tight loops.
TS
2. Question: Describe a scenario where using a Set is demonstrably superior to using a
List for storing sObject records retrieved from a query, beyond simple duplicate removal.
Consider performance implications.
A S
C
Answer: A scenario involves processing related records based on a unique identifier
retrieved from an initial query, where the order doesn't matter, and you need fast lookups.
C E
For example, querying Contact records related to multiple Account IDs, and then needing
R
to quickly check if a specific Contact (identified perhaps by Email or another unique field)
was part of the retrieved set before performing further actions.
O
S F
Superiority: While a List allows duplicates and maintains insertion order, checking for the
existence of an element ( [Link](element) ) requires iterating through the list (O(n)
complexity).
L E
S A
A Set , which stores unique elements, uses a hash-based structure. Checking for existence
( [Link](element) ) is typically much faster, approaching O(1) complexity on
average. When dealing with thousands of records where frequent existence checks are
needed (e.g., inside loops processing other data), using a Set for the queried IDs or
records significantly improves performance compared to iterating through a List
repeatedly.
Answer:
Approach 1: Safe Navigation Operator ( ?. ): Apex supports the safe navigation operator
( ?. ). This operator short-circuits the expression if the left-hand side is null, returning null
instead of throwing a NullPointerException .
Example: String ownerName = [Link]?.Owner?.Name; . If [Link]
is null, ownerName becomes null. If [Link] is null, ownerName becomes
null.
Approach 2: Formula Fields: If the nested data is frequently needed and primarily for display
or simple logic, creating a formula field on the base object (e.g., a formula field
Account_Owner_Name__c on Contact with the formula [Link] ) can
encapsulate the navigation.
Salesforce handles the null checks internally. This is useful for Visualforce pages, Lightning
components, or reporting, but less flexible for complex Apex logic where intermediate objects
might be needed.
Approach 3 (Less Common/More Complex): Using dynamic Apex and Schema methods to
TS
check field accessibility and presence before attempting access, although this is generally
overkill for simple null checking compared to the safe navigation operator.
A S
4. Question: Explain the concept of transient variables in Apex classes. Provide a specific
C
use case where using the transient keyword is crucial for functionality or avoiding errors,
C E
particularly concerning View State or asynchronous processing.
Answer: The transient keyword in Apex declares instance variables that should not be
R
saved and transmitted as part of the object's state.
O
S F
Serialization is the process of converting a variable (like a list, object, or map) into a
format that can be stored or transferred — typically as a string of text or binary.
E
In the context of Visualforce:
L
S A
Serialization means converting your Apex variables into a format (text-based) so they
can be stored in the page’s View State and sent between the browser and the server.
Visualforce View State: Variables in a controller or extension that are not marked
transient are serialized and included in the View State, which is sent back and forth
between the client and server. Large or complex objects (like web service response
wrappers, large collections for temporary processing) can bloat the View State, hitting
the limit (135KB) and causing errors or performance degradation. Marking such
temporary, non-essential-for-page-rerender variables as transient excludes them from
the View State.
Asynchronous Apex (Batchable, Queueable): When an Apex class implements
interfaces like [Link] or Queueable , its member variables are serialized
when the job is queued. If a variable holds data that cannot or should not be serialized
(e.g., certain non-serializable types like SaveResult[] if not handled carefully, or
sensitive data you don't want stored long-term in the job queue), marking it transient
prevents serialization errors or data persistence issues. The variable will be null when
the job executes unless re-initialized within the execution context (e.g., in the execute
method).
Crucial Use Case: In a Visualforce controller managing a complex multi-step wizard, you
might retrieve extensive data from a callout for one step. This data is processed and
summarized, but the raw, large response object isn't needed for subsequent steps or page
re-renders. Marking this raw response object variable as transient is crucial to prevent
View State explosion.
5. Question: Compare and contrast Map<Id, sObject> and Map<String, sObject> for
storing and accessing sObjects based on a specific field value (other than Id). What are the
implications for query construction and data retrieval?
Answer:
TS
A S
Map<Id, sObject> : This is the standard and most efficient way to map sObjects when
you have their Ids. The key is the Id data type. It's ideal after querying records where
the Id is readily available. Lookups are direct and fast.
C
Map<String, sObject> : This is used when you need to map sObjects based on a non-
Implications:
OR
S F
Query Construction: When using Map<String, sObject> , you typically query the records
first, then iterate through the results to populate the map, using the desired field value as the
L E
key. Example: Map<String, Account> accountByExternalId = new Map<String,
Account>(); for(Account acc : [SELECT Id, Name, External_ID__c FROM Account
S A
WHERE External_ID__c != null]) { [Link](acc.External_ID__c,
acc); } . This requires an extra iteration step compared to directly getting a Map<Id,
sObject> from a query like new Map<Id, Account>([SELECT Id FROM Account]) .
Data Retrieval/Access: Accessing records from Map<Id, sObject> uses the Id .
Accessing from Map<String, sObject> uses the string key (e.g.,
[Link]('EXT123') ).
Uniqueness: Map<Id, sObject> inherently handles unique Ids. For Map<String,
sObject> , you must ensure the chosen String field is truly unique within the context you're
using it; otherwise, later records with the same key will overwrite earlier ones in the map
during population.
Use Case: Map<String, sObject> is essential for integration scenarios where you need to
relate incoming data (using an external ID) to existing Salesforce records without querying
by that external ID repeatedly in loops.
Answer: A selective SOQL query is one where the WHERE clause includes filters on indexed
fields, and these filters significantly reduce the number of records scanned by the database.
Salesforce query optimizer uses indexes to quickly locate matching records without scanning
the entire table. For standard fields, certain fields like Id, Name, OwnerId, CreatedDate,
LastModifiedDate, RecordTypeId, and External ID fields are indexed automatically. Custom
fields need to be marked as External ID or have a custom index requested via Salesforce
Support to be indexed (excluding certain types like multi-select picklists, long text areas).
TS
The operator is selective (e.g., = , IN on a small list, range operators < , > , <= , >= on
A S
The filter condition significantly reduces the result set. For standard indexes, the threshold is
typically < 10% of the first million records and < 5% beyond that, down to a max of 333,333
records. For custom indexes, it's generally < 10% of the total records up to 1 million.
C
Using leading wildcards ( %value ) on indexed text fields makes the filter non-selective as the
index cannot be used effectively.
C E
* Using != or NOT IN on indexed fields is generally non-selective.
OR
Non-Selective Query Example: SELECT Id FROM Account WHERE Name LIKE '%Universal
Containers%' (leading wildcard on an indexed field).
S F
Optimization: If possible, avoid the leading wildcard. If searching for names starting with
'Universal Containers', use Name LIKE 'Universal Containers%' . If searching anywhere,
L E
consider if SOSL is more appropriate, or if a different field (e.g., a dedicated keyword field
populated by triggers/batch) could be indexed and used. If the %value% pattern is
S A
unavoidable and performance is critical on large objects, explore external search solutions or
custom indexing strategies (potentially involving formula fields or helper fields if applicable,
though often complex).
7. Question: Describe how to use SOQL Polymorphism with TYPEOF expressions. Provide
a practical use case where this feature is significantly more efficient or cleaner than
alternative approaches.
Answer: SOQL Polymorphism using TYPEOF allows querying fields specific to different
object types within a single query when dealing with lookup fields that can point to multiple
object types (polymorphic relationships), such as the Who or What fields on Task and
Event , or the ParentId on ContentDocumentLink .
The TYPEOF expression is used within the SELECT clause. It allows specifying different field
lists based on the runtime type of the related record.
Practical Use Case: Building a custom activity timeline component that displays details from
related Leads or Contacts associated with Tasks. Without TYPEOF , you would need to:
TS
3. Separate the WhoId s based on [Link] into a Set<Id> for Leads and a Set<Id>
for Contacts.
A S
4. Perform two additional queries: one for Leads ( SELECT Name, Email, Company FROM
C
Lead WHERE Id IN :leadIds ) and one for Contacts ( SELECT Name, Email,
[Link] FROM Contact WHERE Id IN :contactIds ).
5. Combine the results in Apex logic.
C E
R
Using TYPEOF , you achieve this in a single query. The result includes the specific fields
requested for each type directly within the Task record structure (accessed via [Link]
O
which will be typed as Lead or Contact appropriately). This reduces query count (SOQL
F
limit), simplifies the code, and improves performance by fetching all required data at once.
S
L E
8. Question: Explain the limitations of relationship queries (sub-queries and parent queries)
in SOQL, particularly concerning the number of levels, total records returned, and potential
S A
performance impacts on Large Data Volumes (LDVs).
Levels: You can query up to 5 levels of parent-to-child relationships (e.g., Account ->
Contacts -> Cases -> CaseComments -> Attachments ). You can query up to 5 levels
up the hierarchy for parent relationships (e.g., [Link] ).
Total Records: While the main query is subject to the 50,000 record limit, sub-queries
also contribute to the total heap size limit (6MB sync, 12MB async). More importantly,
sub-queries have their own implicit limits on the number of child records returned per
parent record. While not explicitly documented as a fixed number like the main limit,
fetching tens of thousands of child records for a single parent record in a sub-query can
lead to errors or performance issues. The system may truncate results or throw errors if
a sub-query tries to retrieve an excessive number of children for one parent.
Performance on LDVs: Relationship queries on objects with Large Data Volumes
(LDVs) can be slow if not carefully constructed.
Parent queries ( [Link] ) are generally efficient if the relationship
field ( AccountId on Contact ) is indexed (which it is).
Sub-queries ( (SELECT Id FROM Contacts) ) require the database to find all
children for each parent record returned by the main query. If the parent query
returns many records, and each has many children, this can lead to significant
database load and slow performance, potentially timing out. Filters within the sub-
query's WHERE clause are crucial for performance on LDVs.
Semi-Joins/Anti-Joins: Queries using IN or NOT IN with a sub-query (e.g., SELECT
Id FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE LastName
= 'Jones') ) have specific performance characteristics and limitations, especially
regarding the types of fields allowed in the sub-query's WHERE clause.
Optimization Strategy: For LDVs, always filter parent queries selectively. For sub-queries,
filter them as much as possible using indexed fields within the sub-query's WHERE clause. If
TS
retrieving large numbers of children is necessary, consider breaking the operation down,
possibly using Batch Apex or targeted queries based on parent IDs processed in chunks.
A
9. Question: When would you choose SOSL over SOQL? Describe a complex search S
C
scenario involving multiple object types and unstructured text where SOSL is the only viable
or significantly better option.
C E
Answer: SOSL (Salesforce Object Search Language) is chosen over SOQL when:
request.
OR
1. You need to search for a term across multiple object types simultaneously in a single
S F
2. The search term's location is unknown – it could be in various text-based fields (Name,
Phone, Email, custom text fields, etc.) across different objects.
L E
3. You are searching against fields that are not typically filterable or indexed for SOQL in
the way needed (e.g., searching within long text areas efficiently for a keyword).
S A
4. You need relevance ranking of results.
Complex Scenario: Imagine a support agent needs to find all information related to a specific
error code,
Apex Part 2
Section 1: Apex Fundamentals & Advanced Concepts
(Continued)
DML Operations (Bulkification, Error Handling, Transactions)
10. Question: Explain the concept of "Partial Success" in DML operations using
[Link](records, allOrNone) . Describe a scenario where allowing partial
success ( allOrNone = false ) is necessary, and detail how you would handle the results to
identify and process both successful and failed records.
Answer: By default, DML operations like insert records; are atomic; if any single record
in the list fails (due to validation rules, triggers, etc.), the entire operation rolls back, and no
records are committed to the database. The Database class methods (e.g.,
[Link] , [Link] , [Link] ) provide an optional second
parameter, allOrNone (a Boolean). When set to false , the DML operation allows for
partial success. This means if some records in the list cause errors, only those specific
records will fail, while the valid records will still be committed to the database. The operation
does not throw an exception for the failures; instead, it returns an array of
[Link] (or [Link] , [Link] ) objects,
one for each record processed.
TS
Scenario: Consider importing a list of 100 Lead records from an external system via an API
integration. Some leads might fail Salesforce validation rules (e.g., missing required fields,
A S
invalid email format), while others are perfectly valid. If allOrNone was true (or the default
DML insert leads; was used), a single invalid lead would prevent all 100 leads from
being inserted. Using [Link](leadsToInsert, false); allows the valid leads to
be created.
C
C E
Handling Results: You must iterate through the returned [Link][] array.
Each SaveResult object corresponds to the record at the same index in the input list. The
OR
isSuccess() method indicates if the operation succeeded for that specific record. If
isSuccess() is true, getId() returns the new record ID. If isSuccess() is false,
S F
getErrors() returns a list of [Link] objects detailing the reasons for failure
(e.g., error code, message, fields involved). Robust handling involves iterating the results,
L E
collecting the IDs of successful records, and logging or reprocessing the failed records along
with their specific error messages.
S A
List<Lead> leadsToInsert = // ... list of leads from external source
[Link][] saveResults = [Link](leadsToInsert, false);
// Allow partial success
11. Question: Describe transaction control in Apex. How are DML operations, SOQL
queries, and callouts related within a single transaction? Explain the use and significance of
Savepoint and [Link]() .
TS
Answer: An Apex transaction represents a set of operations that are executed as a single
unit. It begins when Apex code starts execution (e.g., trigger invocation, web service call,
A S
Visualforce action) and ends when all code finishes. Within a transaction, all DML operations
are grouped together. If the transaction completes successfully, all DML changes are
C
committed to the database. If an unhandled exception occurs, or a [Link]()
is executed, all DML changes made within that transaction are rolled back, leaving the
E
database in the state it was in before the transaction started.
C
Relationship between Operations:
OR
DML & SOQL: SOQL queries within a transaction see the database state at the start of
S F
the transaction, plus any DML changes already made within the same transaction that
have not been rolled back. They do not see changes committed by other concurrent
L E
transactions until the current transaction completes.
Callouts: A crucial rule is that you cannot perform a DML operation before making a
S A
callout in the same transaction. If you need to perform DML and then a callout, the
callout must typically be moved to a separate, asynchronous transaction (e.g., using
@future or Queueable Apex). You can perform a callout first and then perform DML.
Answer: The "Mixed DML Operation" error occurs when you try to perform DML operations
on both setup objects (like User, Profile, PermissionSet) and non-setup objects (like Account,
TS
Contact, Custom Objects) within the same transaction. Salesforce enforces this restriction to
prevent potential security issues and data inconsistencies that could arise from mixing setup
Common Scenarios: C
C E
1. User Creation/Update with Related Records: A process attempts to create a new User
OR
record and, in the same transaction (e.g., within the same method or trigger execution),
also create or update related non-setup records like Contacts or custom objects linked to
S F
that user. For instance, an Apex service called from a Lightning component tries to insert
a User and then immediately insert a related User_Profile__c custom object record.
L E
2. Automated Permission Assignment: A trigger on a standard object (e.g., Opportunity)
tries to update the Opportunity record itself (non-setup DML) and also assign a
S A
Permission Set to the Opportunity Owner (User) or another related user (setup DML via
PermissionSetAssignment ).
1. Using @future Methods: Perform one type of DML (e.g., non-setup) in the current
synchronous transaction. Then, call an @future method, passing necessary IDs or
data. Inside the @future method (which runs in a separate transaction), perform the
other type of DML (e.g., setup). Example: Update the Opportunity synchronously, then
call @future updateUserPermissions(oppOwnerId); to handle the
PermissionSetAssignment insertion asynchronously.
2. Using Queueable Apex: Similar to @future , but offers more flexibility (chaining jobs,
passing complex types). Perform one DML type synchronously, then enqueue a
Queueable job to perform the other DML type. This is often preferred over @future for
more complex scenarios or when state needs to be maintained.
Important Note: Ensure the asynchronous method is designed correctly, receiving only
necessary primitive data types (like Ids) if using @future , and handling potential errors and
limits within its own transaction context.
Answer: Trigger recursion occurs when a DML operation within a trigger causes the same
trigger (or another trigger on the same object) to fire again, potentially leading to a loop. For
example, if an after update trigger on Account updates a field on the same Account
TS
records that triggered it, this update action will cause the after update trigger to fire again
for those records. If there's no control mechanism, this can repeat until Salesforce limits are
hit (e.g., maximum trigger depth exceeded) or stack depth limits occur.
A S
C
Uncontrolled Recursion Scenario: An after update trigger on Opportunity updates the
Description field of the same Opportunity based on some criteria. This update fires the
loop.
C E
after update trigger again, which might perform the same update, leading to an infinite
OR
A common and effective technique is to use a static Boolean variable within a helper class
S F
(or the trigger handler class) to track whether the trigger has already run within the current
transaction context for a specific execution path.
L
// Helper Class
E
} S A
public class TriggerHandlerHelper {
public static boolean isExecuting = false;
// Trigger
trigger OpportunityTrigger on Opportunity (after update) {
if (![Link]) {
[Link] = true; // Set flag
if (![Link]()) {
update oppsToUpdate; // This DML might cause recursion
}
TS
When the trigger fires the first time, isExecuting is false, so the code runs, sets
S
isExecuting to true, and performs the DML. If the DML causes the trigger to fire again
within the same transaction, the check if (![Link]) will
A
now be false, preventing the logic inside the if block (including the DML) from running
C
again, thus breaking the recursive loop. Static variables retain their value throughout a single
transaction context.
C E
14. Question: Describe the benefits of using a Trigger Framework (like the one proposed by
OR
Tony Prophet or others). How does it improve maintainability, testability, and adherence to
best practices compared to putting all logic directly in the .trigger file?
S F
Answer: A Trigger Framework provides a structured approach to managing Apex trigger
L E
logic, typically by moving the actual processing code out of the .trigger file itself and into
separate handler classes. The .trigger file becomes minimal, primarily responsible for
S A
delegating execution to the framework's handler methods based on the trigger context
( [Link] , [Link] , [Link] , [Link] , etc.).
Benefits:
1. Maintainability: Logic is organized into distinct classes and methods, often grouped by
functionality or context (e.g.,
[Link]([Link]) ). This makes it easier to
locate, understand, and modify specific pieces of logic without wading through a
monolithic trigger file. Adding new functionality often involves adding a new method or
modifying an existing one in the handler, rather than complex conditional logic in the
trigger.
2. Testability: Handler classes and their methods can be instantiated and tested
independently of the trigger firing mechanism. This allows for focused unit tests that
provide specific inputs and assert expected outputs or behaviors without needing
complex DML setup to invoke the trigger indirectly. Mocking dependencies becomes
easier.
3. Adherence to Best Practices: Frameworks naturally encourage bulkification (passing
[Link] , [Link] , etc., to handler methods), help manage trigger order
of execution (by controlling the sequence of method calls within the handler), and
simplify recursion control (often built into the framework's entry point).
4. Reusability: Common logic or utility functions can be placed in separate service classes
called by multiple handler methods or even different trigger handlers, promoting DRY
(Don't Repeat Yourself).
5. Readability: The main .trigger file becomes very clean, showing only the delegation
calls, making the entry points clear. The handler classes provide the detailed
implementation.
Comparison: Without a framework, .trigger files often become large, complex, and
TS
difficult to manage, especially as requirements grow. Logic for different events ( before
insert , after update ) gets intertwined, testing becomes harder as you must simulate
C
15. Question: Outline the guaranteed Order of Execution for triggers and other automation
C E
(like Workflow Rules, Process Builder, Flows) within a single transaction, especially during
an update operation that might fire multiple automations. Why is understanding this order
OR
critical for debugging and designing complex logic?
Answer: Salesforce has a well-defined, though complex, order of execution for operations
S F
within a transaction. Understanding this is critical because automations can interact, modify
data sequentially, and impact the inputs or conditions for subsequent steps. Debugging
E
unexpected behavior often requires tracing data changes through this sequence.
L
S A
Simplified Order for an Update Operation (Key Steps):
1. Load Record: The original record is loaded from the database (or represents the initial
state before the update DML).
2. Request Updates: New field values are provided from the request (e.g., UI, API, Apex
update statement).
3. System Validation: Salesforce runs system validation rules (checking required fields,
field formats, maximum field lengths). Custom validation rules are not run yet.
4. Before Triggers: All before update Apex triggers execute.
5. System Validation (Again): System validation rules run again (important if before
triggers modified fields).
6. Duplicate Rules: Duplicate rules execute.
7. Save (No Commit): The record is saved to the database, but not yet committed.
8. After Triggers: All after update Apex triggers execute.
9. Assignment Rules: Assignment rules execute.
10. Auto-Response Rules: Auto-response rules execute.
11. Workflow Rules: Workflow rules execute. If a workflow updates a field, this restarts the
update process (steps 4-11, plus parts of 12 & 13) for that specific record before moving
to the next rule. Before and after triggers fire again.
12. Process Builder / Flows (Record-Triggered - Actions): Processes and record-
triggered flows whose criteria are met execute actions. If a process/flow updates a field
on the record, this also restarts the update process (similar to workflow field updates,
firing triggers again).
13. Escalation Rules: Escalation rules execute.
14. Entitlement Rules: Entitlement rules execute.
15. Roll-Up Summary Fields: If the record is part of a roll-up summary field (or becomes
part of one), the parent record's roll-up field is calculated. The parent record goes
through its own save procedure, including its own triggers and automation.
E
messages, and enqueuing asynchronous Apex ( @future , Queueable) happen after the
commit.
R C
Criticality: Knowing this order helps predict how data changes. For example, an after
F O
update trigger might rely on a field value that could be subsequently changed by a
Workflow Rule or Process Builder action before the transaction fully commits. Designing
E S
logic often requires deciding whether an action should happen before the save (in a before
trigger) or after the save but potentially before other automations (in an after trigger), or
A L
after everything else (asynchronously). Debugging involves checking the state of the record
at each relevant stage (e.g., using debug logs between trigger execution and workflow
S
execution).
Apex Part 4
Section 1: Apex Fundamentals & Advanced Concepts
(Continued)
Classes & Objects (OOP, Design Patterns, Modifiers)
16. Question: Explain the difference between virtual , abstract , and override
keywords in Apex classes and methods. Provide a scenario where using an abstract class
with abstract methods is more appropriate than using a virtual class with virtual
methods.
Answer: These keywords define inheritance behavior in Apex Object-Oriented
Programming:
virtual : When applied to a class, it means the class can be extended (inherited from).
When applied to a method within a virtual or abstract class, it means the method
can be overridden by a subclass. The virtual method itself provides a default
implementation.
abstract : When applied to a class, it means the class cannot be instantiated directly; it
must be extended by a subclass. An abstract class can contain both regular methods
(with implementation) and abstract methods. An abstract method has no
implementation (no method body, just the signature ending with a semicolon) and must
be overridden by any concrete (non-abstract) subclass.
override : This keyword is used in a subclass method signature to indicate that it is
providing a new implementation for a virtual or abstract method inherited from the
A S
Scenario for Abstract vs. Virtual: An abstract class is more appropriate when you want to
C
define a common template or contract for a group of related subclasses, but the core
behavior defined by certain methods must be implemented differently by each subclass, and
C E
providing a default implementation in the parent makes no sense or is impossible. For
example, consider a framework for processing different types of payment methods
( PaymentProcessor ).
OR
S F
// Abstract base class - cannot be instantiated directly
public abstract class PaymentProcessor {
E
// Common properties or methods with implementation
L
public Decimal amount { get; set; }
public String currencyCode { get; set; }
S A
public void logTransactionStart() {
[Link]("Starting payment processing for amount: " + amount);
}
A S
C
public override [Link] processPayment() {
[Link]("Processing bank transfer...");
// Specific logic for bank transfer
// ... return actual SaveResult
return null; // Placeholder
C E
}
}
OR
S F
Here, PaymentProcessor defines a contract. Every payment processor must have a way to
L E
processPayment() , but the implementation is entirely specific to the type (Credit Card vs.
Bank Transfer). Making processPayment() abstract enforces this. A virtual method
S A
might be used if there was a sensible default way to process a payment, which subclasses
could optionally change, but that doesn't fit this scenario well. Using an abstract class
prevents developers from accidentally trying to instantiate a generic PaymentProcessor
which wouldn't know how to actually process anything.
17. Question: Describe the Singleton design pattern in Apex. How can you implement it,
and what are its potential drawbacks, especially in the context of Salesforce governor limits
and testing?
Answer: The Singleton pattern ensures that a class has only one instance and provides a
global point of access to it. In Apex, this is typically implemented using:
// Private constructor
private SettingsManager() {
// Load settings, e.g., from Custom Settings or Custom Metadata
settings = new Map<String, String>();
// Example: Query Custom Metadata
for (My_Setting__mdt setting : [SELECT DeveloperName, Value__c
FROM My_Setting__mdt]) {
[Link]([Link], setting.Value__c);
TS
S
}
[Link]("SettingsManager initialized.");
}
S F
L E
// Public method to access settings
public String getSetting(String key) {
}
S
}
Areturn [Link](key);
// Usage:
// SettingsManager mgr = [Link]();
// String apiKey = [Link]("API_Key");
Potential Drawbacks:
Despite drawbacks, singletons are useful for managing shared resources like application
settings, caching, or utility services where a single point of control is desired, provided the
limit and testing implications are handled.
TS
18. Question: Explain the purpose and usage of the final keyword with variables,
A S
methods, and classes in Apex. How does final differ from const (which doesn't exist in
Apex)?
C
after initialization.
C E
Answer: The final keyword in Apex is used to indicate that an entity cannot be changed
OR
Final Variables: A final variable (instance or static) must be initialized exactly once,
either at the point of declaration or within a constructor (for instance variables) or a static
S F
initializer block (for static variables). After initialization, its value (for primitives) or its
reference (for objects) cannot be changed. For object references, this means the
L E
variable cannot be reassigned to point to a different object, but the internal state of the
object it points to can still be modified (unless the object itself is immutable).
S A
public class Example {
public final Integer MAX_RETRIES = 5; // Initialized at
declaration
public final Account defaultAccount;
private static final String DEFAULT_STATUS;
Difference from const (Conceptual): Apex does not have a const keyword like C++ or
JavaScript. The concept of const often implies compile-time constants whose values are
TS
fixed and known at compile time. Apex final variables are initialized at runtime (when the
class is loaded or an object is instantiated) and their value might depend on runtime
A S
conditions (like the accountId passed to the constructor in the example). While final
prevents reassignment, it doesn't guarantee the value is a compile-time constant. The
closest Apex has to compile-time constants are static primitive variables, but even those are
C
initialized at runtime when the class loads. static final primitives are the common way to
define constants in Apex.
C E
Testing (Advanced Techniques, Mocking, Async Testing)
OR
19. Question: Explain the purpose of the @TestSetup annotation. What are its advantages
S F
over creating test data directly within each @isTest method, and what are the key
limitations or considerations when using it?
L E
Answer: The @TestSetup annotation identifies a method used to create common test
S A
records that are available to all other test methods within the same test class. This setup
method executes once before any @isTest methods in the class run.
Advantages:
1. Data Rollback: Records created in @TestSetup are rolled back after the entire test
class finishes execution, not after each individual test method. This means changes
made to these records by one @isTest method are visible to subsequent @isTest
methods run within the same execution context (though Salesforce aims to run tests in
parallel isolation where possible, this isn't always guaranteed, especially with
SeeAllData=true or certain platform interactions). It's generally best practice for test
methods to treat @TestSetup data as read-only or to query it fresh if modifications are
needed for a specific test.
2. Method Signature: The @TestSetup method must be static and return void .
3. Callouts: Callouts cannot be made from a @TestSetup method.
TS
A S
4. Single Method: Only one method per test class can have the @TestSetup annotation.
5. Async Considerations: While the data is available, testing asynchronous operations
C
might require careful handling, as the async job runs in a separate context which might
not automatically see the @TestSetup data unless queried explicitly using IDs passed
from the test method.
C E
OR
20. Question: Describe how to use the HttpCalloutMock interface to test Apex code that
makes HTTP callouts. Provide an example of implementing the respond method to
S F
simulate both a successful response and an error response based on the request.
Answer: The HttpCalloutMock interface is a crucial part of the Apex testing framework
L E
that allows you to intercept HTTP callouts made during a test run and provide predefined
mock responses. This prevents tests from making actual external calls (which is disallowed
S A
by default and undesirable for reliability and cost) and allows you to simulate various
scenarios, including success, errors, different status codes, and specific response bodies.
To use it:
@isTest
global class YourMockImplementation implements HttpCalloutMock {
global HTTPResponse respond(HTTPRequest req) {
// Create a response object
HttpResponse res = new HttpResponse();
[Link]('Content-Type', 'application/json');
TS
S
} else if ([Link]().contains('error_endpoint')) {
// Simulate a client error (e.g., Bad Request)
[Link](400);
[Link]('Bad Request'); C A
[Link]('{"error":"Invalid input", "code":"400"}');
} else {
C E
// Default: Simulate a server error
"code":"500"}');
OR
[Link]('{"error":"Internal Server Error",
[Link](500);
}
S F
[Link]('Server Error');
L E
return res;
A
}
S
Example Usage in Test Method:
@isTest
static void testMyCalloutLogic() {
// Set the mock callout class
[Link]([Link], new YourMockImplementation());
A S
C
This setup allows testing the logic that processes callout responses without any external
dependency.
C E
Apex Part 5
OR
Section 1: Apex Fundamentals & Advanced Concepts
(Continued)
S F
L E
Testing (Advanced Techniques, Mocking, Async Testing)
(Continued)
S A
21. Question: How do you effectively test asynchronous Apex operations like @future ,
Queueable, and Batch Apex? Explain the roles of [Link]() , [Link]() ,
and how to handle assertions for code executed asynchronously.
Answer: Testing asynchronous Apex requires special handling because the asynchronous
code runs in a separate transaction after the main test method transaction completes. The
[Link]() and [Link]() methods are crucial for this.
[Link]() : This method marks a point in your test code from which a fresh set
of governor limits is applied. More importantly for async testing, it signals the start of the
section where you will enqueue or execute your asynchronous operation.
[Link]() : This method signals the end of the test setup and action phase.
Crucially, when [Link]() is executed, all asynchronous processes (like
@future calls or Queueable jobs) that were initiated after the preceding
[Link]() are forced to execute synchronously before the test method
proceeds further. Batch Apex execute methods also run.
Testing Process:
1. Setup Data: Use @TestSetup or create necessary test data within the @isTest
method before [Link]() .
2. Start Test: Call [Link]() .
3. Invoke Asynchronous Code: Call the method annotated with @future , enqueue the
Queueable job using [Link]() , or execute the Batchable using
[Link]() .
4. Stop Test: Call [Link]() . At this point, the asynchronous code that was
invoked between start and stop will run to completion.
5. Assertions: After [Link]() , perform SOQL queries or check system state to
TS
verify the results of the asynchronous operation. Since the async code has now
completed synchronously, you can directly assert its effects (e.g., check if records were
created/updated, fields have expected values, etc.).
A S
Example (Testing a Queueable):
C
@isTest
static void testMyQueueable() {
C E
// 1. Setup Data
R
Account acc = new Account(Name="Test Account");
insert acc;
O
// 2. Start Test
S F
E
[Link]();
L
S A
// 3. Invoke Asynchronous Code
MyQueueableJob job = new MyQueueableJob([Link]);
[Link](job);
// 4. Stop Test
[Link](); // Forces the [Link]() method to run
now
// 5. Assertions
// Query the record again to check the results of the queueable job
Account updatedAcc = [SELECT Id, Description FROM Account WHERE Id =
:[Link]];
[Link]("Processed by Queueable", [Link],
"Account description should be updated by the queueable job.");
// Check other side effects, e.g., related records created by the job
}
For Batch Apex, [Link]() ensures the execute method runs (potentially multiple
times depending on scope size and batch size used in [Link] ). You can
then query the results or check AsyncApexJob status if needed, although direct verification
of data changes is more common.
Answer: Dealing with governor limits effectively often requires more than just basic
bulkification. Here are three advanced strategies:
TS
too many operations (complex calculations, multiple callouts, heavy DML across various
objects), break it down using asynchronous Apex ( @future , Queueable, Batch). For
A S
example, if updating an Account triggers complex recalculations on hundreds of related
Opportunities and Cases, the trigger could simply enqueue a Queueable job passing the
C
Account ID. The Queueable job then runs in its own transaction with fresh limits,
E
performing the heavy lifting without impacting the initial trigger transaction limits.
Chaining Queueable jobs can handle even larger tasks sequentially.
R C
2. Efficient Collection Processing & Map-Based Lookups: When processing records
retrieved from a query and needing related data, avoid querying inside loops at all costs.
F O
Instead, collect the necessary IDs from the initial set of records (e.g., collect all
AccountId s from a list of Contact s). Perform a single bulk query for all related records
E S
(e.g., SELECT Id, Name FROM Account WHERE Id IN :accountIds ). Store these
related records in a Map (e.g., Map<Id, Account> accountMap = new Map<Id,
A L
Account>(queriedAccounts); ). Then, iterate through your original list and use
[Link](record.RelatedId__c) to efficiently retrieve the related data in memory
S
without additional queries per record. This drastically reduces SOQL query counts.
3. Lazy Loading & Selective Querying: Don't query more data or fields than you
absolutely need for the specific operation. Use WHERE clauses effectively to minimize
record count. Only include fields in the SELECT clause that are actually used by the
logic. For complex UIs or processes, consider