Advanced Apex Anti-Patterns - The Invisible Performance Killers

Advanced Apex Anti-Patterns

The Invisible Performance Killers

Three tricky architectural traps that silently drain CPU limits and crash multi-tenant transactions.

Trap 1: The `SObjectType.getRecordTypeInfosByDeveloperName()` Loop Bleed

We are taught to avoid hardcoding IDs by using Schema Describe methods. It looks clean, declarative, and completely safe. But what happens when you place describe calls inside a business logic loop?

❌ The Flawed Anti-Pattern:
for (Account acc : trigger.new) {
    // Hidden Danger: Re-instantiating schema maps 200+ times per batch execution context
    Id corporateId = Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName()
                        .get('Corporate_Account').getRecordTypeId();
    if (acc.RecordTypeId == corporateId) { /* Process */ }
}
Why it's a Trap: While Salesforce caches Schema describes internally, calling `.getRecordTypeInfosByDeveloperName()` generates an entirely new map instance in heap memory every single time it is invoked. In a high-volume batch or a heavy trigger sequence, this completely leaks CPU time and will push you straight past the 10-second standard CPU governor limit.
The Architectural Fix:

Cache the Record Type ID **exactly once** in a static variable outside of your collection loops, or leverage a Lazy Loading singleton pattern:

// Cache outside the loop
private static final Id CORP_RECTYPE_ID = Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName()
                                            .get('Corporate_Account').getRecordTypeId();

for (Account acc : trigger.new) {
    if (acc.RecordTypeId == CORP_RECTYPE_ID) { /* Constant-time evaluation */ }
}

Trap 2: The Inner-Join SOQL Parent Subquery Heap Explosion

You need to process Accounts alongside all of their related Contacts. To be efficient with governor limits, you write a single, optimized parent-child subquery.

❌ The Flawed Anti-Pattern:
// Looks like a standard loop, right?
for (Account acc : [SELECT Id, Name, (SELECT Id, Email FROM Contacts) FROM Account WHERE Rating = 'Hot']) {
    for (Contact con : acc.Contacts) {
        // Business logic here
    }
}
Why it's a Trap: Developers assume a SOQL standard `for` loop dynamically chunks data into 200-record sets to safeguard heap allocation boundaries. However, **this safety mechanism breaks on child subqueries**. If a few parent Accounts in that query happen to own 15,000 child Contacts each, Salesforce attempts to load those nested child relationships entirely into active memory simultaneously, generating an uncatchable `System.Apex.HeapSizeLimitExceeded` crash.
The Architectural Fix:

When dealing with high child density, flip the query architecture to drive execution from the **child object level** up to the parent using standard dot-notation, allowing true 200-record transaction stream chunking:

// Query the child directly to guarantee seamless stream chunking
for (Contact con : [SELECT Id, Email, Account.Id, Account.Name FROM Contact WHERE Account.Rating = 'Hot']) {
    // Safely processes exactly 200 contact records at a time with a flat heap footprint
}

Trap 3: Dynamic String Concatenation vs. Query Bind Variables

When designing custom filter UIs or dynamic reporting endpoints, you frequently need to construct Database queries using ad-hoc strings.

❌ The Flawed Anti-Pattern:
Set<String> statusSet = new Set<String>{'Active', 'Pending'};
String query = 'SELECT Id FROM Case WHERE Status IN (';
for(String s : statusSet) {
    query += '\'' + s + '\',';
}
query = query.removeEnd(',') + ')';
List<Case> caseList = Database.query(query); // Vulnerable & unoptimized
Why it's a Trap: Beyond the glaring risk of SOQL injection attacks, compiling manual string predicates forces the database routing engine to re-parse, optimize, and generate unique execution plans for every minor variation of that string sequence. It also artificially balloons your transaction heap size with messy string manipulation operations.
The Architectural Fix:

Salesforce's database engine can evaluate local code memory collections directly inside dynamic execution strings using **Apex Bind Variables**. Pass the collection variable reference explicitly:

Set<String> statusSet = new Set<String>{'Active', 'Pending'};

// Clean, secure, and utilizes pre-compiled database query execution plans
String secureQuery = 'SELECT Id FROM Case WHERE Status IN :statusSet';
List<Case> caseList = Database.query(secureQuery);

💡 Senior Architect Rules of Thumb

When designing for scale inside Salesforce, remember that local syntax parsing speed is only half the battle. True performance tuning requires protecting the platform's shared memory boundaries. Minimize deep object graphs in dynamic SOQL loops, cache your structural meta-describes, and let the runtime compiler handle data binding securely.

Comments

Popular posts from this blog

Communicating between Independent LWC in Omniscript

JWT (JSON Web Token)

Efficient way to write apex code

Import third party JS library in OmniScript Custom Lightning Web Components

Server-Side Document Generation

Salesforce Best Features available

Reusable Code in OmniScript - Lightning Web Components

Mastering the Matrix: Top 10 Advanced Salesforce Integration Interview Questions