35 Advanced Salesforce Developer Scenarios (Post 2)

35 Advanced Salesforce Developer Scenarios (Post 2) | Apex Patterns, Triggers, Integration, Security, Testing

35 Advanced Salesforce Developer Scenarios

Post 2: Apex Patterns, Trigger Choreography, Integration, Security & Testing

35 Advanced Questions
Q36–Q70 Interview Scenarios
Production Patterns & Edge Cases
100% Free No Signup Required

What's Post 2: Deep-dive into production-grade Salesforce architecture. We've covered debugging basics (Post 1). Now let's tackle: How do you design Apex for performance and testability? How do you choreograph multi-trigger interactions without creating chaos? How do you build integrations that don't lose data? How do you lock down security without breaking user workflows? How do you deploy without breaking prod?

35 Questions in 5 Advanced Categories:
• 💎 Advanced Apex & Design Patterns (Q36–Q42)
• 🎭 Multi-Trigger Choreography & Order of Execution (Q43–Q49)
• 🔗 Complex Integrations & Webhook Patterns (Q50–Q56)
• 🔐 Security & Permission Edge Cases (Q57–Q63)
• 🚀 Testing, Code Quality & Deployment (Q64–Q70)

Pair with these free resources: Apex 120+ Q&ASOQL 81+ Q&ALWC Zero to HeroPractice Zone (27 Quizzes)

💎

Advanced Apex & Design Patterns

7 Questions
36

Static Inner Classes vs. Sharing Mode — When Does Sharing Bypass Matter?

Advanced
Direct Answer
A static inner class inherits the sharing mode of the outer class. If outer class is with sharing, inner class respects sharing rules. If outer class is without sharing, inner class can query records the caller can't see. Use static inner classes sparingly for utility logic; for data access, always declare with sharing explicitly.
Why?

Salesforce enforces Org-Wide Defaults and sharing rules at the SOQL level. A with sharing class can only query records the running user owns or has access to. A without sharing class bypasses all sharing logic (admin power). Inner classes inherit the outer class's mode, so if you're not careful, a utility inner class could accidentally bypass sharing and expose data.

This matters in multi-tenant orgs where users shouldn't see each other's records, or in communities where guests have restricted access.

Code Example
// ❌ WRONG — Inner class inherits without sharing, bypasses security public without sharing class OrderService { public static class OrderUtil { public static List<Order__c> getAllOrders() { return [SELECT Id FROM Order__c]; // Returns ALL orders, even ones user shouldn't see } } } // ✓ CORRECT — Explicitly declare inner class with sharing public with sharing class OrderService { public static class OrderUtil { // Inherits with sharing from outer class public static List<Order__c> getUserOrders() { return [SELECT Id FROM Order__c]; // Returns only orders user can access } } } // ✓ CORRECT — If utility needs without sharing, declare explicitly public with sharing class OrderService { public static List<Order__c> getPublicOrders() { return getAllOrdersForAdmin(); } private static List<Order__c> getAllOrdersForAdmin() { return new AdminUtil().getAllOrders(); } } public without sharing class AdminUtil { public List<Order__c> getAllOrders() { return [SELECT Id FROM Order__c]; // Explicit without sharing, intentional access } }
Key Points
  • Inner classes inherit outer class's sharing mode (with/without).
  • without sharing bypasses Org-Wide Defaults and sharing rules — use sparingly.
  • When calling a without sharing utility, document why and audit access.
  • Best practice: keep utility classes small, explicitly declare sharing mode, test with different user profiles.
Real World Example

XYZ Company: Built an OrderUtil inner class to calculate discounts. Outer OrderService class was without sharing (for admin batch processing). OrderUtil inherited without sharing, so it could query all orders. A community user called OrderService, expecting to see only their orders — instead they saw all company orders. Security breach.

Fix: Made OrderService with sharing (default), moved admin utilities to a separate AdminOrderUtil class explicitly marked without sharing with code comments explaining why.

"Inner classes inherit outer class sharing mode. If outer is without sharing and inner queries data, it bypasses sharing rules — document and test carefully."
37

Builder Pattern for Complex Object Creation — When Is It Overkill?

Advanced
Direct Answer
Builder pattern is useful for objects with many optional fields (3+ constructor params). Overkill for simple objects (1-2 fields). Benefit: fluent, readable chaining. Cost: extra class complexity. Use for Account/Order objects, not for wrapper utility classes.
Code Example
// ❌ WRONG (too many params, hard to read) Order__c order = new Order__c('123', 'ABC', 50000, 'USD', true, false, true); // ✓ CORRECT (Builder pattern, readable) Order__c order = new OrderBuilder() .accountId('123') .name('ABC') .amount(50000) .currency('USD') .isActive(true) .build(); public class OrderBuilder { private String accountId; private String name; private Decimal amount; private String currency; private Boolean isActive; public OrderBuilder accountId(String val) { this.accountId = val; return this; } public OrderBuilder name(String val) { this.name = val; return this; } public Order__c build() { return new Order__c(Name = name, Amount = amount, ...); } }
Key Points
  • Use Builder for 3+ optional constructor params (Account, Order, Opportunity).
  • Overkill for simple utility classes or DTOs with 1-2 fields.
  • Builder adds ~50 lines of boilerplate; worth it for complex objects.
  • Fluent API (method chaining) improves readability in test setup.
"Builder pattern is useful for 3+ optional constructor params. Readable, maintainable. Overkill for simple objects. Consider complexity cost vs. readability gain."
38

Static Caching in Apex — Cache Strategy for Frequently Queried Data?

Advanced
Direct Answer
Static variables persist for entire transaction. Cache lookup tables (RecordTypes, Profiles, custom metadata) in a static Map on first access. Subsequent lookups hit memory (0ms) instead of SOQL (10-100ms). Trade-off: must invalidate cache if data changes (triggers, batch jobs).
Code Example
// ❌ WRONG — Query RecordType every time for (Order__c order : orders) { RecordType rt = [SELECT Id FROM RecordType WHERE Name = 'Standard']; order.RecordTypeId = rt.Id; } // 100 queries for 100 records (SOQL governor limit hit) // ✓ CORRECT — Static cache, query once public class OrderService { private static Map<String, Id> rtCache; public static void setRecordTypes(List<Order__c> orders) { if (rtCache == null) { rtCache = new Map<String, Id>(); for (RecordType rt : [SELECT Id, Name FROM RecordType WHERE SObjectType = 'Order__c']) { rtCache.put(rt.Name, rt.Id); } } for (Order__c order : orders) { order.RecordTypeId = rtCache.get('Standard'); // 0ms memory lookup } } public static void invalidateCache() { rtCache = null; // Call from trigger on RecordType changes } }
Key Points
  • Static caching = SOQL reduction. Query once, reuse for lifetime of transaction.
  • Best for: RecordTypes, Profiles, custom metadata, lookup reference data.
  • Cache invalidation: on trigger (RecordType updated), batch job completion, scheduled refresh.
  • Monitor: Log cache hits/misses to tune strategy.
"Cache lookup tables (RecordTypes, Profiles) in static Map. Query once on first access, reuse for entire transaction. Remember to invalidate cache if underlying data changes."
39

Mock vs. Stub in Unit Tests — When Do You Use Each?

Medium
Direct Answer
Mock: verifies method was called with expected params (behavior verification). Stub: returns fake data (state verification). Use mocks for callouts (verify API called correctly), stubs for SOQL (return test data). In Apex, use `HttpCalloutMock` for callouts, `@isTest` for SOQL mocking.
Code Example
// Mock: Verify HTTP callout was made public class APICalloutMock implements HttpCalloutMock { public HttpResponse respond(HttpRequest req) { return new HttpResponse(); } } @isTest static void testAPICallout() { Test.setMock(HttpCalloutMock.class, new APICalloutMock()); // Verifies: API was called, correct endpoint, method, headers OrderService.syncToERP(orders); System.assertEquals(1, [SELECT COUNT() FROM Error__c]); // No errors } // Stub: Return fake data for SOQL @isTest static void testOrderCalculation() { Order__c testOrder = new Order__c(Name = 'Test', Amount = 1000); insert testOrder; // Returns test data from database (stub via @isTest database) Decimal margin = OrderService.calculateMargin(testOrder.Id); System.assertEquals(250, margin); // Test logic, not database }
Key Points
  • Mock = verify behavior (was method called with right params?).
  • Stub = provide fake data (return test values for calculations).
  • Use mocks for external callouts (HTTP, APIs). Use stubs for data layer.
"Mocks verify behavior (was API called?), stubs provide data (return test values). Use HttpCalloutMock for callouts, @isTest database for SOQL stubs."
40

Governor Limits in Different Contexts — Trigger vs. Batch vs. Queueable?

Advanced
Direct Answer
Trigger: 10k SOQL, 10k DML, 12MB heap, shared with calling code. Batch: 10k SOQL, 10k DML, 12MB heap per batch (independent). Queueable: 10k SOQL, 10k DML, 12MB heap, 3600s timeout (independent). Batch and Queueable have isolated limits; triggers don't.
What Controls It
  • Trigger: Shares DML/SOQL limits with calling code. Update 100 records via UI = 100 SOQL/DML available for trigger. If trigger uses 50 SOQL, only 50 left for batch/process.
  • Batch: Each batch execution has independent 10k SOQL, 10k DML, 12MB heap. No sharing with other batches or triggers.
  • Queueable: Same as batch, but chainable (up to 5 times). Each chain restarts the 10k/10k/12MB limits.
  • Heap Limit: 12MB for triggers, batch, Queueable. Static variables accumulate; memory leaks can cause failure at 11MB.
Real World Example

XYZ Company: Batch job updated 100k orders, calling trigger 100k times (one trigger per order). Trigger tried to query Accounts (10k SOQL) to update parent totals. Batch had used 8k SOQL already, only 2k left. Trigger failed on record 20k: "SOQL limit exceeded."

Fix: Moved Account update logic to a separate Queueable enqueued from trigger finish(). Queueable has independent 10k SOQL limit, no conflict with batch limits.

"Triggers share limits with calling code (10k SOQL/DML total). Batch and Queueable have isolated limits. For heavy operations, move to Queueable to avoid limit contention."
41

Dependency Injection in Apex — When and How?

Medium
Direct Answer
Dependency injection (DI) = pass dependencies to a class constructor instead of hardcoding them. Makes classes testable and decoupled. Useful for callout services, data access layers. In Apex, use constructor injection or property injection. Overkill for simple, single-responsibility classes.
Code Example
// ❌ WRONG — Tightly coupled, hard to test public class OrderSyncService { public void syncToERP(List<Order__c> orders) { HttpRequest req = new HttpRequest(); req.setEndpoint('https://erp.example.com/orders'); HttpResponse res = new Http().send(req); // Can't mock Http.send in test } } // ✓ CORRECT — Injected dependency, testable public class OrderSyncService { private IHttpClient httpClient; public OrderSyncService(IHttpClient httpClient) { this.httpClient = httpClient; } public void syncToERP(List<Order__c> orders) { HttpResponse res = httpClient.send(req); // Can mock IHttpClient in test } } public interface IHttpClient { HttpResponse send(HttpRequest req); } public class MockHttpClient implements IHttpClient { public HttpResponse send(HttpRequest req) { HttpResponse res = new HttpResponse(); res.setStatusCode(200); return res; } } @isTest static void testSyncWithMockClient() { OrderSyncService service = new OrderSyncService(new MockHttpClient()); service.syncToERP(orders); // Test passes, no real HTTP calls }
Key Points
  • DI = pass dependencies to constructor, don't hardcode them.
  • Makes classes testable (can inject mocks) and reusable.
  • Use for callout services, data access layers, integrations.
  • Overkill for simple utility classes.
"Inject dependencies via constructor to make classes testable and decoupled. Use for callouts and data access layers. Overkill for simple utilities."
42

Apex Enum vs. String Constants for Status Values?

Medium
Direct Answer
Enums are type-safe, prevent typos, and compile-time checked. String constants are flexible, pickable, queryable. For business logic (code-only status values), use enums. For field picklists (user-editable, shared with SFDC UI), use string constants matching the field values.
Code Example
// ✓ CORRECT — Enum for code-only logic (type-safe) public enum OrderStatus { DRAFT, SUBMITTED, APPROVED, REJECTED } public class OrderValidator { public void validateOrder(Order__c order, OrderStatus status) { if (status == OrderStatus.DRAFT) { ... } // Compile error if typo (OrderStatus.DRFT won't compile) } } // ✓ CORRECT — String constant matching field picklist public class OrderConstants { public static final String STATUS_DRAFT = 'Draft'; public static final String STATUS_SUBMITTED = 'Submitted'; // Matches Order__c.Status field picklist values } [SELECT Id FROM Order__c WHERE Status = :OrderConstants.STATUS_DRAFT]; // Queryable, matches UI picklist
Key Points
  • Enums = type-safe, compile-time checked, no typo risk.
  • String constants = queryable, matchable with field picklists.
  • Use enums for internal state logic, string constants for Salesforce field values.
"Use enums for code-only logic (type-safe, no typos). Use string constants for field picklists (queryable, matches UI values)."
🎭

Multi-Trigger Choreography & Order of Execution

7 Questions
43

Order of Execution Across Before/After Triggers on Multiple Objects

Advanced
Direct Answer
Execution order: (1) Validation rules, (2) Before triggers, (3) Record saved, (4) After triggers, (5) Child records saved, (6) After-child triggers. Across multiple objects: depends on relationship depth. Parent before → Parent after → Child before → Child after. If you update child from parent after-trigger, child after-trigger fires, but parent after-trigger doesn't re-fire (no recursion).
Why?
Understanding execution order is critical for avoiding cascading updates and logic errors. Before-triggers fire before DML, so you can modify data before saving. After-triggers fire after DML, so you can query related records and update children. If you update parent from child after-trigger, parent after-trigger doesn't re-fire — Salesforce prevents infinite loops by not re-firing after-triggers.
Key Points
  • Before: validate, modify data before save. After: query relationships, update children.
  • After-trigger updates don't re-fire the same after-trigger (no recursion on same object).
  • Parent before → After → Child before → After (depth-first order).
  • Document execution order in code comments; it's fragile.
"Order: Before triggers → Save → After triggers → Children saved → After-child triggers. After-trigger updates don't re-fire same after-trigger to prevent infinite loops."
44

Platform Events vs. Publish-Subscribe Pattern for Decoupled Triggers

Advanced
Direct Answer
Platform Events decouple triggers: Order trigger publishes event, separate handler subscribes and processes. Benefits: Order trigger doesn't care who listens, testing easier, new handlers added without modifying Order trigger. Trade-off: ~1-10s delivery latency (eventually-consistent). Use for async notifications. For sync logic, direct trigger-to-trigger calls are faster but tightly coupled.
Code Example
// Platform Event decouple pattern trigger OrderTrigger on Order__c { if (Trigger.isAfter && Trigger.isInsert) { List<Order_Event__e> events = new List<Order_Event__e>(); for (Order__c o : Trigger.new) { events.add(new Order_Event__e(OrderId__c = o.Id)); } EventBus.publish(events); // Publishes event, doesn't wait for handlers } } // Separate subscriber public class OrderEventHandler { @InvocableMethod(label='Process Order Event') public static void handleOrderEvent(List<Order_Event__e> events) { // Process event, update related records, call API // Decoupled from OrderTrigger logic } }
Key Points
  • Platform Events decouple triggers (loose coupling, easy testing).
  • ~1-10s latency (not real-time). Use for async workflows.
  • New subscribers don't require trigger changes (extensible).
  • Direct trigger calls are faster but create tight coupling.
"Platform Events decouple triggers via pub-sub pattern. Trade async latency for loose coupling. New handlers don't require trigger code changes. Use for notifications and workflows."
45

Trigger Updates Parent, Parent Update Fires Trigger Again — No Recursion Guard?

Advanced
Direct Answer
After-trigger updates don't re-fire the same after-trigger (Salesforce built-in recursion prevention). But if Child trigger updates Parent and Parent trigger updates Child, two-way loop is created. Add recursion guards (Set<Id>) on both triggers to track processed records and break cycle.
Key Points
  • Salesforce prevents same after-trigger from re-firing (auto recursion prevention).
  • But two-way updates (A→B→A) create loops not prevented automatically.
  • Use Set<Id> guards on both triggers to track processed records.
"Salesforce prevents same after-trigger from re-firing. But two-way updates (Parent→Child→Parent) still loop. Add Set<Id> guards on both triggers to break the cycle."
46

Test Apex Order of Execution — Full Cascade of Before/After Fires?

Medium
Direct Answer
@isTest code mirrors production execution order. Before triggers fire, record saves, after triggers fire, child records save, child after-triggers fire. Use Test.startTest() / Test.stopTest() to isolate async (batches, futures, Queueables). In tests, check Trigger.isBefore/isAfter/isInsert/isUpdate to verify logic branches.
Key Points
  • @isTest executes in same order as production.
  • Test.startTest() starts fresh governor limits (for async testing).
  • Assert at each stage: after before-trigger (data modified?), after after-trigger (children updated?).
"@isTest mirrors production execution order. Use Test.startTest()/stopTest() for async. Assert after each trigger stage to verify cascading logic."
47

Process Builder / Flow Trigger Timing — Sync or Async?

Medium
Direct Answer
Process Builder and Flows are sync-to-after-triggers. They fire after all after-triggers complete. If Flow creates records, those records' triggers fire before Flow finishes. Use Flows for orchestration, not for dependent logic on record DML. For async decoupling, use Platform Events or Queueables.
Key Points
  • Process Builder / Flows fire after all triggers (sync, not async).
  • If Flow creates records, those records' triggers run before Flow completes.
  • For true async, use Platform Events or Queueable from trigger.
"Process Builder and Flows fire sync after all triggers. They block the transaction. For async, use Platform Events or Queueables enqueued from trigger."
48

Bulkification — Single Loop vs. Batch Processing Pattern

Medium
Direct Answer
Bulkification = batch processing, not row-by-row. Query once (SOQL), update all (DML) instead of querying per record in loop. Reduces SOQL count (10k limit), DML calls, lock contention. Pattern: Query all related records in one SOQL, build Map by ID, loop through trigger records once (O(n) instead of O(n²)).
Code Example
// ❌ WRONG (row-by-row, 100 SOQL calls) trigger OrderTrigger on Order__c { for (Order__c o : Trigger.new) { Account acc = [SELECT Id FROM Account WHERE Id = :o.AccountId]; acc.LastOrderDate = Today(); update acc; // 100 updates } } // ✓ CORRECT (bulkified) trigger OrderTrigger on Order__c { Set<Id> accountIds = new Set<Id>(); for (Order__c o : Trigger.new) accountIds.add(o.AccountId); Map<Id, Account> accounts = new Map<Id, Account>([ SELECT Id FROM Account WHERE Id IN :accountIds ]); // 1 SOQL for (Order__c o : Trigger.new) { Account acc = accounts.get(o.AccountId); if (acc != null) acc.LastOrderDate = Today(); } update accounts.values(); // 1 bulk update }
Key Points
  • Bulkify: query all IDs at once, loop once, update once.
  • Reduces SOQL count (1 instead of 100), DML calls, lock contention.
  • Use Map<Id, sObject> for O(1) lookups in loop.
"Bulkify triggers: query all IDs once, loop through Trigger.new once, update all at end. Reduce SOQL from 100 to 1, DML calls from 100 to 1. Use Map for O(1) lookups."
49

Trigger Calls Batch Job — Counts Against Apex Jobs Limit?

Medium
Direct Answer
Yes. Trigger can enqueue batch job (Database.executeBatch or System.scheduleBatch). Each counts as 1 Apex job enqueued. Max 50k batches per org per 24 hours. If trigger enqueues 1 batch per insert and you bulk insert 1k orders, you enqueue 1k batches (fine). But looping and enqueueing per record (1k enqueues from 1k inserts) hits limit faster than you'd expect.
Key Points
  • Trigger can enqueue batch. Counts toward 50k batch jobs / 24hr limit.
  • Bulk insert 1k → 1k batch enqueues (OK, well under 50k).
  • Monitor: track batch enqueues per day; if approaching 50k, defer batch to scheduled job.
"Trigger can enqueue batch. Each enqueue counts toward 50k-per-24hr limit. Monitor batch enqueus. If frequent, defer to scheduled job instead."
🔗

Complex Integrations & Webhook Patterns

7 Questions
50

Webhook Idempotency — Duplicate Event Processing & De-duplication

Advanced
Direct Answer
Webhook may be delivered multiple times if processing times out. Use idempotency key (unique ID per event) and check if event was already processed before executing. Store processed event IDs in a queue table (WebhookQueue__c). If duplicate arrives, skip processing, return 200 (success).
Code Example
@RestResource(urlMapping='/webhook/orders') global class OrderWebhookHandler { @HttpPost global static void handleWebhook() { RestRequest req = RestContext.request; String idempotencyKey = req.headers.get('X-Idempotency-Key'); // Check if event already processed List<WebhookQueue__c> existing = [ SELECT Id FROM WebhookQueue__c WHERE IdempotencyKey__c = :idempotencyKey LIMIT 1 ]; if (!existing.isEmpty()) { RestContext.response.statusCode = 200; // Idempotent response return; } // New event, process try { WebhookQueue__c queueItem = new WebhookQueue__c( IdempotencyKey__c = idempotencyKey, Payload__c = req.requestBody.toString(), Status__c = 'Pending' ); insert queueItem; System.enqueueJob(new ProcessWebhookQueueable(queueItem.Id)); RestContext.response.statusCode = 200; } catch (Exception e) { RestContext.response.statusCode = 500; } } }
Key Points
  • Idempotency key = unique ID per event (from webhook sender).
  • Check if key exists before processing (skip duplicates).
  • Return 200 for both first and duplicate deliveries (idempotent API).
  • Log all events in queue table for audit and debugging.
"Use idempotency key to de-duplicate webhook events. Check if key exists before processing. Return 200 for duplicates (idempotent). Log all events to queue table."
51

Dead Letter Queue (DLQ) Pattern for Failed Webhook Events

Advanced
Direct Answer
Dead Letter Queue: webhook events that fail processing after max retries go to a DLQ table for manual review. Separate failed events from successful ones. Allows async retry/recovery. Pattern: WebhookQueue__c has Status (Pending/Processed/Failed). After 3 failed retries, move to Failed status. Admin reviews DLQ, manually retries or deletes.
Key Points
  • DLQ = permanent storage for failed events (audit trail).
  • After max retries, move to Failed status (don't infinitely retry).
  • Admin manually reviews DLQ, decides to retry or discard.
  • Prevents data loss; every event is tracked.
"Store failed webhook events in Dead Letter Queue (DLQ). After max retries, mark as Failed. Admin reviews DLQ for manual recovery or audit."
52

Long-Running Integration — Polling vs. Streaming vs. Webhooks

Advanced
Direct Answer
Polling: periodically query external API for changes (scalable, high latency). Streaming: keep connection open to external API (real-time, connection-intensive). Webhooks: external API calls Salesforce on event (real-time, requires public endpoint). Choose based on data freshness needs and external system capabilities. Hybrid: poll + webhook for real-time with fallback.
Key Points
  • Polling: simple, high latency (1-15 min), limits external API calls.
  • Streaming: real-time, connection overhead, risk of drops.
  • Webhooks: real-time, requires public URL, idempotency needed.
  • Hybrid: webhook primary, poll as fallback for missed events.
"Choose integration pattern based on freshness: Polling (high latency, scalable), Streaming (real-time, resource-heavy), Webhooks (real-time, idempotency required). Hybrid = best of both."
53

Transactional Integrity Across Salesforce & External System

Advanced
Direct Answer
Two-phase commit: (1) Record created in Salesforce, (2) API call to external system. If API fails, rollback Salesforce record. Challenge: Salesforce DML already committed. Solution: Store status in Salesforce (Pending), call API async (Queueable), update status on API response (Synced/Failed). If API fails, manual retry or scheduled reprocessing.
Key Points
  • Salesforce DML commits immediately, no true 2-phase commit.
  • Pattern: create in Salesforce (Pending), call API async, update status (Synced/Failed).
  • If API fails, log error, retry via scheduled job or manual intervention.
  • Accept eventual consistency; data may be temporarily out-of-sync.
"Salesforce can't guarantee transactional integrity with external systems. Pattern: create record (Pending), call API async, update status (Synced/Failed). Manual retry on failure."
54

REST API Versioning — Breaking Changes & Backward Compatibility

Medium
Direct Answer
Always version REST endpoints (/api/v1, /api/v2). Add new fields but don't remove old ones (backward compat). When breaking changes needed, bump version number. Consumers stay on old version until they upgrade. Support old version for 6+ months (deprecation window).
Key Points
  • Version in URL (/api/v1). Makes breaking changes safe.
  • Add fields, don't remove. Old consumers skip unknown fields.
  • Deprecation window: support old version for 6+ months.
"Version REST APIs (/api/v1). Add fields, don't remove them. Maintain old version for 6+ months (deprecation window) before sunsetting."
55

Rate Limiting Integration — Backoff Strategy & Throttling

Medium
Direct Answer
Extract Retry-After header from 429 response. Wait before retrying (don't hammer API). Exponential backoff: 2s, 4s, 8s, etc. For Salesforce-side throttling, use Queueable queue (serialized) to naturally respect rate limits. Monitor API usage, alert if approaching limits.
Key Points
  • 429 = rate limited. Extract Retry-After header.
  • Exponential backoff (2s, 4s, 8s) prevents overwhelming API.
  • Queueable queue = serialized callouts (natural rate limiting).
  • Monitor usage, alert before hitting limits.
"Handle rate limiting: extract Retry-After header from 429 response. Implement exponential backoff. Use Queueable for serialized, rate-limited API calls."
56

Webhook Signature Verification — Prevent Unauthorized Calls

Medium
Direct Answer
External system signs webhook payload (HMAC-SHA256). Salesforce endpoint verifies signature using shared secret. If signature invalid, reject webhook (403 Forbidden). Pattern: signature = HMAC-SHA256(payload + secret), external system includes signature in header, Salesforce validates before processing.
Key Points
  • Signature verification = prevent spoofed webhooks.
  • External system signs payload with shared secret.
  • Salesforce verifies before processing (reject if invalid).
  • HMAC-SHA256 is standard; Crypto.generateMac() in Apex.
"Verify webhook signatures to prevent spoofed calls. External system signs payload with HMAC-SHA256 + secret. Salesforce validates before processing. Reject if invalid."
🔐

Security & Permission Edge Cases

7 Questions
57

Guest User Sees Records via SOQL — Field-Level Security Bypassed?

Advanced
Direct Answer
Guest users in communities can see records if sharing rules allow, but SOQL still respects Field-Level Security (FLS). If FLS hides a field, guest sees NULL (not error). SOQL doesn't throw error for FLS violation; it silently excludes hidden fields. Code must explicitly check FLS before using sensitive fields.
Key Points
  • SOQL respects sharing rules AND FLS. Guest can see record but not sensitive fields.
  • Hidden fields return NULL, no error thrown (silent failure risk).
  • Code must check FLS before using field values (Schema.SObjectField.isAccessible()).
  • Test with actual guest user profile to catch FLS issues.
Real World Example

XYZ Company: Guest user queried Orders and salary was returned (should be hidden). Root cause: SOQL didn't check FLS for Salary field. Guest saw NULL silently; developer assumed NULL meant "no value" instead of "no access." Fixed by checking SObjectField.isAccessible() before returning field.

"SOQL respects FLS silently—hidden fields return NULL, not error. Always check SObjectField.isAccessible() before using sensitive fields in code exposed to guest/community users."
58

Admin User Runs Code WITH SHARING — Still Bypass Records?

Medium
Direct Answer
NO. Admin user running with sharing code respects Org-Wide Defaults and sharing rules. Admin can't bypass sharing by code; admin must override sharing in Salesforce UI (Setup > Security > Sharing Settings). WITH SHARING always enforces security, regardless of user role.
Key Points
  • with sharing enforces OWD and sharing rules for ALL users, including admins.
  • Admin override (Setup > Sharing Settings) is different from code-level override.
  • Code can't bypass admin override settings.
"with sharing enforces security for all users, including admins. Code can't bypass sharing. Admin override requires Setup > Security > Sharing Settings changes."
59

Org-Wide Defaults Conflict with Sharing Rules — Which Wins?

Medium
Direct Answer
Sharing rules are exceptions to Org-Wide Defaults (OWD). OWD sets default access (Private/Read-Only/Read-Write). Sharing rules grant additional access beyond OWD. If OWD = Private (no access), sharing rule can grant Read access. If OWD = Read-Write (full access), sharing rule can't restrict (can't deny access).
Key Points
  • OWD = default access level (baseline).
  • Sharing rules = exceptions (grant more access, can't deny).
  • If OWD = Private, sharing rule can grant Read. If OWD = Read-Write, sharing rule can't restrict.
"OWD sets baseline access. Sharing rules grant exceptions (can add access, not remove). If OWD=Private, sharing rule can grant Read. If OWD=Read-Write, sharing rule can't restrict."
60

RLS (Record-Level Security) on Lookup Field — Can User See Related Record?

Advanced
Direct Answer
No. If parent record has RLS that hides it from user, user can't see parent via lookup relationship. SOQL follow-up (SELECT Id, Account.Name) returns NULL for Account.Name if user lacks access. Relationship queries respect RLS + sharing rules on related object.
Key Points
  • Lookup fields inherit parent's sharing. If parent is hidden, lookup value is hidden.
  • Relationship queries (SELECT Account.Name FROM Order) respect parent's RLS.
"Lookup relationships inherit parent object's sharing. If parent record is hidden by RLS, lookup values are hidden. User sees NULL for related field values."
61

SOQL Executed as User vs. SYSTEM Context — Sharing Difference?

Medium
Direct Answer
Execution context determines SOQL behavior. @isTest(SeeAllData=true) executes as SYSTEM (sees all data). Trigger executes as the user who modified data (respects user's sharing). REST API called by community user executes as that user (respects their sharing). SOQL always respects current execution user's sharing unless explicitly bypassed (without sharing class).
Key Points
  • Execution context = current user's sharing is enforced in SOQL.
  • @isTest(SeeAllData=true) bypasses security (testing only).
  • Community user REST API = executes as that user, respects their access.
"SOQL respects current execution user's sharing. Trigger = triggering user's sharing. REST API = calling user's sharing. @isTest(SeeAllData=true) sees all data for testing."
62

Custom Permission Bypass Sharing Rules?

Medium
Direct Answer
No. Custom permissions are separate from sharing. A user with a custom permission still respects sharing rules on records. Use custom permissions for features/features-flags, not to bypass data access. If you need to bypass sharing, use WITHOUT SHARING class (explicit, auditable).
Key Points
  • Custom permissions = feature flags, not data access control.
  • Custom permission + sharing rule = user sees data AND feature available.
  • To bypass sharing, use WITHOUT SHARING class (intentional, documented).
"Custom permissions don't bypass sharing. Use them for feature flags. To bypass sharing, explicitly use WITHOUT SHARING class with documentation."
63

Trigger Updates Record as Different User — Sharing Respected?

Medium
Direct Answer
Trigger runs as the user who modified the record (can't change execution user in code). However, WITH SHARING still enforces that user's sharing rules. If that user lacks access to related records, trigger's SOQL respects their sharing. To query as admin, use WITHOUT SHARING in a separate class.
Key Points
  • Trigger executes as triggering user, can't override.
  • WITH SHARING respects that user's sharing rules.
  • To query beyond user's access, move to WITHOUT SHARING class (intentional bypass).
"Trigger runs as triggering user. WITH SHARING respects their sharing rules (can't change user in code). Use separate WITHOUT SHARING class to query beyond user's access."
🚀

Testing, Code Quality & Deployment

7 Questions
64

Code Coverage 75% but Production Bug — What Missed?

Advanced
Direct Answer
75% code coverage doesn't mean 75% of logic is tested. Coverage ≠ quality. Missed: exception handling (catch blocks), governor limit branches, callout failures, trigger recursion guards, SOQL null-checks. Use @isTest(SeeAllData=false) and test failure paths, not just happy paths.
Key Points
  • Code coverage % = lines executed, not logic quality.
  • Test happy paths + error paths (exceptions, governor limits).
  • Test boundary conditions (empty lists, null values, gov limit edge cases).
  • Coverage != correctness. 75% coverage can still have production bugs.
"Code coverage ≠ code quality. 75% coverage misses exception paths, governor limits, null checks. Test happy + error paths. Test boundary conditions."
65

Test Data Setup — Should Use Actual Records or Minimal Test Data?

Medium
Direct Answer
Use minimal test data (only fields needed for test). Reduces test runtime, isolates behavior, makes tests readable. @TestSetup for shared test data across multiple test methods. Avoid copying prod data to tests (may violate privacy, slow tests). Mock external dependencies (HTTP, APIs).
Key Points
  • Minimal test data = faster tests, clearer intent.
  • @TestSetup for shared data across test methods.
  • Mock external dependencies (HTTP, APIs) — don't call real APIs in tests.
  • Avoid prod data copy; use @isTest(SeeAllData=false) by default.
"Use minimal test data. @TestSetup for shared setup. Mock external APIs. Avoid copying prod data. Minimal data = faster, clearer tests."
66

Performance Testing in CI/CD — How to Catch Performance Regressions?

Advanced
Direct Answer
Test SOQL + DML counts (Limits.getSOQLStatements(), Limits.getDMLStatements()). Assert expected limits before/after batch jobs. Benchmark slow reports (expected runtime <10s). For callouts, mock external calls and assert retry logic. Track deployment history: if 1.5s batch becomes 15s after code change, catch it before prod.
Key Points
  • Assert SOQL/DML counts (Limits.getSOQLStatements()) in tests.
  • Benchmark critical flows (batch, report, API endpoint).
  • Track deployment performance: if code change increases query count, reject deploy.
  • Load test with production-scale data in sandbox.
"Assert SOQL/DML counts in tests. Benchmark critical flows. Load test with prod-scale data. Track performance: if query count increases post-deploy, rollback."
67

Feature Flags in Apex — Gradual Rollout Without Code Deploy?

Medium
Direct Answer
Use custom metadata or custom settings for feature flags. Apex code checks flag at runtime: if (FeatureFlags__mdt.get('EnableNewQuoteLogic').IsEnabled__c). Deploy code with new feature disabled. Enable flag in production without code redeploy. Allows gradual rollout, A/B testing, instant rollback.
Key Points
  • Store feature flags in custom metadata (config) or custom settings (data).
  • Check at runtime: if (flag.IsEnabled__c) { ... }.
  • Deploy with flag OFF. Enable in prod without redeploy.
  • Allows gradual rollout, A/B testing, instant disable on bug.
"Use custom metadata for feature flags. Deploy with flag OFF. Enable in prod without code redeploy. Allows gradual rollout and instant rollback without redeployment."
68

Deployment Validation Errors — How to Debug Deployment Issues?

Medium
Direct Answer
Common errors: Code coverage (< 75%), Field reference errors (nonexistent field), Test failure (code broke existing logic), Managed package conflicts. For each error: (1) Read Salesforce error message carefully, (2) Search deployment details for line number, (3) Reproduce in sandbox, (4) Fix and re-deploy. Always validate in full sandbox first.
Key Points
  • Validate deploy in full sandbox first (catches 90% of issues).
  • Code coverage must be 75%+ (org-wide). Single class can be 0% if other classes cover 75%+.
  • Check field references, test failures, managed package conflicts.
  • Deploy errors are verbose; read message + line number carefully.
"Validate in sandbox first (catches 90% of errors). Common: code coverage < 75%, nonexistent field, test failure, package conflict. Read error message + line number. Reproduce in sandbox, fix, redeploy."
69

Rollback Strategy — What If Production Deploy Breaks Production?

Advanced
Direct Answer
No native rollback in Salesforce. Rollback = deploy previous version (redeploy last known-good code). For data integrity: triggers/processes should log all changes (Error__c custom object). On production bug: (1) Disable via feature flag (instant), (2) Identify root cause, (3) Redeploy fix, (4) Revert flag. For data damage: restore from backup (Recycle Bin has 15-day retention).
Key Points
  • No native Salesforce rollback. Rollback = redeploy previous code.
  • Instant mitigation: disable feature flag (code still deployed, logic off).
  • Data restore: Recycle Bin (15 days), backup service (3rd party).
  • Log all changes for audit trail (error tracking, data quality).
"No native rollback in Salesforce. Mitigate with feature flag (instant disable). Rollback = redeploy previous code. Restore data via Recycle Bin (15 days) or backup service."
70

Deployment Frequency — Weekly vs. Daily Small Releases?

Medium
Direct Answer
Small, frequent deploys (daily/multiple times per day) = lower risk, faster feedback, easier debugging. Big, monthly deploys = higher risk, changes pile up, harder to isolate bugs. Best practice: trunk-based development with feature flags. Deploy main branch daily (feature OFF). Enable flags gradually in production. Rollback is revert flag, not redeploy code.
Key Points
  • Daily small deploys < monthly big deploys (lower risk, faster feedback).
  • Trunk-based: merge to main when ready, deploy daily.
  • Feature flags allow code to be deployed but logic OFF until ready.
  • Rollback = disable flag (instant), not redeploy (slow).
"Daily small deploys > monthly big deploys. Use trunk-based development with feature flags. Deploy code daily, enable flags gradually. Rollback = disable flag (instant)."
SF
By SF Interview Pro
Salesforce Interview Prep Team
Practical Q&A by working Salesforce professionals · LWC, Apex, Data Cloud & AI
About Us ↗
☕ Enjoyed this article?
SF Interview Pro is 100% free and maintained by a Salesforce professional. No ads, no paywalls, and no signup required. If this guide helped you prepare for an interview, earn a certification, or grow your Salesforce career, consider buying me a coffee! ☕💜
🇮🇳 UPI (India)
UPI QR Code to support sfinterviewpro
Pay by QR
GPay · PhonePe · Paytm · BHIM
🌎 International
PayPal QR Code to support sfinterviewpro Pay via PayPal ↗
Scan or tap to pay