If you’re preparing for Salesforce developer interviews or just want to level up your Apex skills, you’re in the right place. In this second part of our Salesforce Apex interview questions series, we’re going beyond the basics. This post covers topics like Apex integrations, trigger frameworks, bulk data processing, SOQL performance tips, and even LWC-related concepts. Let’s dive in.
1. What is the purpose of Salesforce Outbound Messages?
Salesforce Outbound Messages are a way for Salesforce to automatically send information to other systems outside of Salesforce. Think of it as Salesforce proactively ‘telling’ another system something important has happened or some data has changed. It’s used for integrating Salesforce with other applications so that data stays in sync or actions can be triggered elsewhere.
The main purpose is to achieve real-time data synchronization and trigger actions in external systems, ensuring that different applications have up-to-date information without manual intervention.
Example: Imagine you have a company that uses Salesforce for Sales and an external accounting system (could be a SAP ..) to manage invoices and payments. When a Sales Opportunity in Salesforce changes its “Stage” to “Closed Won,” you want to automatically create an invoice for that sale in your accounting system.
2. What is the purpose of the HttpRequest and HttpResponse classes in Apex?
In Apex, the HttpRequest and HttpResponse classes are fundamental for making and handling web service callouts to external systems. They allow your Salesforce code to act as a client, sending requests to other servers and receiving their responses. (OR)
The HttpRequest class in Apex is like a form you fill out to send information to another website or system. It lets you specify where to send data, what data to send, and how to send it. The HttpResponse class is then like the package you receive back from that website or system, containing their reply or the data you requested. Together, they allow Salesforce to talk to other applications over the interne
//simple get request public class CalloutExample { public static void makeGetRequest() { Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://example.com/api/data'); // Add to Remote Site Settings request.setMethod('GET'); request.setHeader('Content-Type', 'application/json'); HttpResponse response = http.send(request); if (response.getStatusCode() == 200) { System.debug('Response: ' + response.getBody()); } else { System.debug('Error: ' + response.getStatus()); } } }
3. What is a wrapper class in Apex, and when would you use it?
A wrapper class in Apex is a custom data type that allows you to combine different data types or objects into a single object. Think of it as a custom container that can hold various pieces of information, even if those pieces come from different Salesforce objects or are just simple variables.
Display related but distinct data together in a user interface (like a Visualforce page or Lightning component). Salesforce standard objects often have fields, but sometimes you need to show data from multiple, unrelated objects, or include non-database values (like a checkbox or a calculated field) alongside record data.
//syntax of wrapper class public class MyWrapperClass { public Account acc { get; set; } public Boolean isSelected { get; set; } // Constructor public MyWrapperClass(Account a) { this.acc = a; this.isSelected = false; } }
4. How does @AuraEnabled(cacheable=true) differ from @AuraEnabled in Apex?
The core difference between @AuraEnabled(cacheable=true) and @AuraEnabled (without cacheable=true) in Apex lies in how and when the results of the Apex method call are stored and retrieved on the client side (your browser or mobile device).
@AuraEnabled: The @AuraEnabled annotation is the fundamental way to make an Apex method callable from client-side Lightning components (Aura Components or Lightning Web Components). Without it, your JavaScript code cannot directly invoke that Apex method.
We can use this when your Apex method performs Data Manipulation Language (DML) operations, like insert, update, delete, or upsert records. Since these operations change data, you always want to ensure the latest state is retrieved from the server. Often used with imperative Apex calls in LWC, where you explicitly call the method and handle the promise.
@AuraEnabled(cacheable=true): This annotation extends @AuraEnabled by enabling client-side caching of the method’s results. It tells Salesforce that the data returned by this Apex method can be safely stored on the client (in the browser’s cache) for a period.
A method marked cacheable=true must not perform any DML operations. It should only retrieve data (e.g., SOQL queries). If you try to perform DML, you’ll get a runtime error. For Lightning Web Components, if you want to use the @wire service to call an Apex method, that method must be annotated with @AuraEnabled(cacheable=true). The @wire service relies on this caching mechanism.
5. How to bypass a trigger in Salesforce?
We can bypass a trigger in Salesforce using a static variable in an Apex class, custom permissions, custom settings/metadata types, or a checkbox field on the object itself. The purpose of bypassing a trigger is usually to prevent unwanted automation when performing specific data operations, like bulk data loads or tests.
Example: Using Custom Settings or Custom Metadata Types
- Create a Hierarchy Custom Setting or a Custom Metadata Type. Add a checkbox field (e.g., Bypass_Account_Trigger__c).
- In trigger, query this custom setting/metadata type to check the value of the checkbox.
- Admins can then enable/disable the trigger bypass simply by updating the record in the custom setting/metadata type.
6. How do you prevent recursion in triggers efficiently?
To prevent recursion in Salesforce Apex triggers most efficiently using a static variable within a helper class. This acts as a flag that ensures our trigger logic runs only once per transaction, even if subsequent DML operations within that same transaction would normally re-fire the trigger.
public class AccountTriggerHandler { // This static variable acts as our flag to prevent recursion public static Boolean hasRun = false; public static void beforeInsertUpdate(List<Account> newAccounts) { // Check the flag. If it's already true, it means the trigger has run in this transaction, so exit. if (hasRun) { return; } // Set the flag to true for the current transaction hasRun = true; // --- our actual trigger logic goes here --- for (Account acc : newAccounts) { if (acc.Description != 'Processed') { acc.Description = 'Processed'; // This update could cause recursion without the flag } } // --- End of our trigger logic --- } }
trigger AccountTrigger on Account (before insert, before update) { // Call the static method from the handler class AccountTriggerHandler.beforeInsertUpdate(Trigger.new); }
7. What happens if a trigger updates the same record that fired it?
If a trigger updates the same record that fired it, it can lead to recursive firing of the trigger. This means the trigger runs again and again, potentially causing an infinite loop and quickly hitting Salesforce governor limits, resulting in an error.
8. What is Database.Stateful, and when should it be used in Batch Apex?
Database.Stateful is an interface in Apex that, when implemented by a Batch Apex class, ensures that the instance member variables maintain their state across different executions of the execute method. Without it, these variables would be reset to their default values for each chunk of records processed.
Example: Let’s say you want to count the total number of Accounts updated by a batch job and log that count in the finish method.
public class AccountUpdateBatchStateful implements Database.Batchable<SObject>, Database.Stateful { // This instance variable's state will persist across execute() calls public Integer totalProcessedCount = 0; public List<Id> failedAccountIds = new List<Id>(); // Example: also collect IDs public Database.QueryLocator start(Database.BatchableContext BC) { return Database.getQueryLocator('SELECT Id, Name FROM Account WHERE LastModifiedDate = TODAY'); } public void execute(Database.BatchableContext BC, List<Account> scope) { List<Account> accountsToUpdate = new List<Account>(); for (Account acc : scope) { try { acc.Description = 'Updated by Stateful Batch'; accountsToUpdate.add(acc); totalProcessedCount++; // This count increments and persists } catch (Exception e) { // Handle errors and collect failed IDs failedAccountIds.add(acc.Id); System.debug('Error processing Account ' + acc.Id + ': ' + e.getMessage()); } } update accountsToUpdate; } public void finish(Database.BatchableContext BC) { System.debug('Total Accounts Processed (CORRECT): ' + totalProcessedCount); if (!failedAccountIds.isEmpty()) { System.debug('Accounts that failed processing: ' + failedAccountIds); // We may can send an email summary here } } }
9. How do you handle Large Data Volumes (LDV) in Salesforce?
1. Optimize Data Model and Schema:
-> We have to use External ID fields for integration keys, as they are automatically indexed and ideal for upsert operations.
-> For very large objects (millions of records) with frequently queried fields, Salesforce Support can enable Skinny Tables.hese are custom tables in the backend that contain a subset of the main table’s fields, specifically designed to improve read performance by reducing the number of joins needed
2. Efficient SOQL and APEX Development:
-> Write SOQL queries that are highly selective. A query is selective if it uses an indexed field in the WHERE clause that filters the result set to a small percentage of the object’s total records (e.g., typically <10% for standard indexes, <33% for custom indexes, or <10 records for a specific lookup).
-> For processing large sets of records, always we have to use Batch Apex, Queueable Apex, or Scheduled Apex rather than attempting to process everything in a single transaction. These async processes help avoid governor limits
3. Bulkification:
-> We have to design all Apex code, Visualforce controllers, and Lightning components to handle multiple records simultaneously rather than one by on
10. What are the different ways to invoke Apex code in Salesforce?
We can invoke Apex code in Salesforce through several different methods, both declarative (point-and-click) and programmatic (coding required)
- Custom Buttons/Links: We can create custom buttons or links on records, list views, or homepage layouts that execute Apex code
- Lightning Web Components (LWC): Use the @wire service (for read-only methods marked @AuraEnabled(cacheable=true)) or imperative calls (for any @AuraEnabled method, especially for DML) from JavaScript to invoke Apex.
- Visualforce Pages: Apex controller methods can be called directly from Visualforce pages using action methods (e.g., {!save}), actionFunction, actionSupport, or JavaScript Remoting.
- Apex Triggers: These are the most common way to automatically execute Apex code. Triggers fire before or after DML operations (insert, update, delete, undelete) on specific Salesforce objects.
- Flows: Salesforce Flow is a powerful automation tool. We can invoke Apex methods directly from a Flow using the “Action” element (calling @InvocableMethod Apex methods)
- Anonymous Apex: We can execute Apex code directly from the Developer Console. This is great for testing snippets of code, performing quick data fixes, or executing one-off scripts.
- Web Services (SOAP/REST API): External systems can invoke Apex methods exposed as web services using the Salesforce APIs. Apex classes or methods can be exposed with @RestResource (for REST API) or webservice keyword (for SOAP API).
11. How can you share a record using Apex Sharing?
We can share a record using Apex Sharing by programmatically creating a Share object (like AccountShare, CustomObject__Share, etc.) and inserting it. This allows you to grant specific access (Read, Read/Write, Full Access) to a particular user, group for a record that isn’t shared by standard sharing rules
//example AccountShare accShare = new AccountShare(); accShare.AccountId = '001XXXXXXXXXXXX'; accShare.UserOrGroupId = '005XXXXXXXXXXXX'; // User or Public Group accShare.AccessLevel = 'Edit'; // Read, Edit accShare.RowCause = Schema.AccountShare.RowCause.Manual; insert accShare;
12. Is a Salesforce trigger synchronous or asynchronous?
A Salesforce trigger is synchronous. It executes in the same transaction as the DML operation that fired it. If an error occurs anywhere within this synchronous chain (e.g., a governor limit is exceeded, or a validation rule fails), the entire transaction is rolled back.
13. Can you call an external API from an Apex Trigger?
No, we cannot directly call an external API from an Apex Trigger. Salesforce enforces a strict rule that prevents callouts (HTTP requests to external systems) from executing synchronously within the same transaction as a DML operation (like an insert or update that fires a trigger). This is to prevent long-running callouts from holding up database transactions, which could lead to performance issues, governor limit breaches, or even deadlocks.
14. What is a List Exception?
A List Exception, in programming contexts, refers to an error that occurs when an operation is performed on a list data structure in a way that violates its rules or boundaries. It’s a type of runtime error, meaning it happens while the program is executing. Click here for more details.
Example: We have 5 books on the shelf, numbered 0 to 4. If you try to grab “book number 7” or “book number -1,” we will get an “index out of bounds” error because those positions don’t exist.
15. What is an interface in Apex?
An interface in Apex is a blueprint of a class. It’s a contract that defines a set of methods that must be implemented by any Apex class that chooses to implement that interface. It specifies what methods a class must have, but not how those methods are implementedExample: Schedulable, Database.Batchable, Comparable interfaces.
Example: Imagine our Salesforce application needs to integrate with different payment gateways (e.g., Stripe, PayPal, ..). Each gateway has its own unique API for processing payments, but from a business perspective, they all perform the “process payment” action.
public interface IPaymentGateway { Boolean processPayment(Decimal amount, String cardNumber, String expiryDate); String generateInvoice(Id orderId); Boolean refundPayment(String transactionId, Decimal amount); }
- StripePaymentGateway implements IPaymentGateway (with Stripe API calls)
- PayPalPaymentGateway implements IPaymentGateway (with PayPal API calls)
public class OrderProcessor { public static void finalizeOrder(Id orderId, Decimal totalAmount, IPaymentGateway gateway) { // No matter which specific gateway is passed, we know it has processPayment Boolean success = gateway.processPayment(totalAmount, '...', '...'); if (success) { String invoice = gateway.generateInvoice(orderId); // Update order status, send email, etc. } else { // Handle payment failure } } }
16. What is Data Binding & Method Overloading?
Data binding and method overloading are two distinct but important concepts in programming.
Data binding is a technique that connects user interface (UI) elements with data sources, so that changes in one automatically reflect in the other. It streamlines development by reducing the need for manual code to synchronize data and presentation.
Method overloading is a feature in object-oriented programming that allows a class to have multiple methods with the same name but with different parameters. These methods perform similar operations but accept different types or numbers of arguments.
17. What is Named Credentials?
Named Credentials in Salesforce simplify callouts to external web services by handling authentication and storing endpoint URLs declaratively. This avoids hardcoding credentials in Apex and improves security and maintainability.
Named Credentials act as a secure placeholder for external service endpoint URLs and their authentication parameters. Instead of embedding these sensitive details directly in your Apex code, you reference a Named Credential by its name
Without Named Credentials:
HttpRequest req = new HttpRequest(); req.setEndpoint('https://api.externalcrm.com/accounts/123'); // Hardcoded URL req.setMethod('GET'); req.setHeader('Authorization', 'Bearer 00DX0000000xxxx'); // Hardcoded/stored credential // ... more setup and then http.send(req)
With Named Credentials (My_External_System):
HttpRequest req = new HttpRequest(); // Salesforce automatically uses the URL and authentication from My_External_System req.setEndpoint('callout:My_External_System/api/accounts/123'); req.setMethod('GET'); // No need to set Authorization header if "Generate Authorization Header" is checked on NC // ... more setup and then http.send(req)
18. What is Apex Transaction Control? Explain Savepoint and Rollback.
An Apex “transaction” is like a single unit of work in Salesforce. Either everything in that unit of work succeeds and is saved, or if anything goes wrong, nothing is saved, and all changes are undone.
Savepoint and Rollback give you more control within a transaction.
Savepoint: Think of this as taking a snapshot of your database changes at a specific moment during your code’s execution. It’s like marking a spot you can return to if things go wrong later.
Rollback: If something bad happens after you’ve set a savepoint, rollback lets you undo all the database changes that happened after that specific savepoint, essentially reverting to that snapshot.
Savepoint sp = Database.setSavepoint(); // Take a snapshot here try { // 1. Create a new Opportunity Opportunity newOpp = new Opportunity(Name = 'Big Deal', StageName = 'Prospecting', CloseDate = Date.today().addDays(30)); insert newOpp; System.debug('Opportunity created: ' + newOpp.Id); // 2. Create related Opportunity Line Items List<OpportunityLineItem> lineItems = new List<OpportunityLineItem>(); lineItems.add(new OpportunityLineItem(OpportunityId = newOpp.Id, Quantity = 1, UnitPrice = 100, PricebookEntryId = '01uXXXXXXXXXXXXXXX')); // Use a valid PBE ID lineItems.add(new OpportunityLineItem(OpportunityId = newOpp.Id, Quantity = 2, UnitPrice = 50, PricebookEntryId = '01uXXXXXXXXXXXXXXX')); insert lineItems; System.debug('Line Items created.'); // 3. Simulate an external callout that might fail Boolean creditCheckPassed = false; // Let's say it failed if (!creditCheckPassed) { throw new CalloutException('Credit check failed for customer.'); } // 4. Update Opportunity status (this line might not be reached if credit check fails) newOpp.StageName = 'Closed Won'; update newOpp; System.debug('Opportunity updated to Closed Won.'); } catch (CalloutException e) { System.debug('External callout failed: ' + e.getMessage()); Database.rollback(sp); // Undo all DML operations *after* the savepoint System.debug('Rolled back to savepoint. Opportunity and Line Items are undone.'); } catch (DmlException e) { System.debug('DML Error: ' + e.getMessage()); Database.rollback(sp); // Undo all DML operations after the savepoint System.debug('Rolled back due to DML error.'); } catch (Exception e) { System.debug('General Error: ' + e.getMessage()); Database.rollback(sp); // Undo all DML operations after the savepoint System.debug('Rolled back due to general error.'); }
19. Explain the difference between LIMIT and FOR UPDATE in SOQL queries.
Both LIMIT and FOR UPDATE are used in SOQL queries, but they do completely different things
LIMIT: This simply restricts the number of records returned by your query. If your query would normally return 100 records, and you add LIMIT 10, it will only give you the first 10 records.
SELECT Id, Name FROM Account LIMIT 5 // Returns at most 5 Accounts
FOR UPDATE: This is much more advanced. When you use FOR UPDATE in a SOQL query, it tells Salesforce to “lock” the records you’re querying exclusively for your current transaction. This prevents other users or processes from changing those same records until your transaction is complete. It’s used to prevent data conflicts when multiple people or systems might try to update the same record at the same time.
SELECT Id, Name, Inventory_Count__c FROM Product__c WHERE Id = :productId FOR UPDATE //Gets the product and locks it, so no one else can update its Inventory_Count__c until our current Apex transaction finishes.
20. Discuss the concept of Platform Events and when you would use them.
Platform Events are a way for different parts of your Salesforce system (or even external systems) to communicate with each other in a loose, publish-and-subscribe way. Think of it like a newspaper: one system “publishes” an event (like “A new Order was placed!”), and other systems or pieces of code that are “subscribed” to that event automatically receive the news and can react to it.
Example: When a Case is created in Salesforce, publish a “New Case Event.” An external monitoring system (subscribed to this event) immediately gets the event and logs it, or another Salesforce org gets it and creates a matching record.
21.Have you ever built a test data factory? Why and how?
Yes, a “Test Data Factory” is a crucial in Salesforce Apex development. It’s basically a special Apex class designed to create fake records that your test methods can use. We build it because testing your code effectively requires data, but we don’t want to rely on real data in your Salesforce org.
A Test Data Factory is typically an Apex class marked with @isTest. It contains static methods that create and often insert sObjects (like Accounts, Contacts, Opportunities, etc.).
@isTest // Mark the class as a test class public class TestDataFactory { // You can add constants or utility methods if needed public static final String DEFAULT_ACCOUNT_NAME = 'Test Account'; // Step 2: Create methods to build individual records // Method to create a single Account public static Account createAccount(String name, String industry) { Account acc = new Account( Name = name, Industry = industry ); return acc; } // Overloaded method for convenience if you only need a name public static Account createAccount(String name) { return createAccount(name, 'Technology'); // Default industry } // Method to create a single Contact public static Contact createContact(String lastName, Id accountId) { Contact con = new Contact( LastName = lastName, AccountId = accountId, Email = lastName.toLowerCase() + '@example.com' ); return con; } // Step 3: Create methods to build and insert lists of records (for bulk testing) public static List<Account> createAndInsertAccounts(Integer numAccounts, String baseName) { List<Account> accounts = new List<Account>(); for (Integer i = 0; i < numAccounts; i++) { accounts.add(new Account(Name = baseName + ' ' + i, Industry = 'Education')); } insert accounts; // Insert them into the test database return accounts; } // Step 4: Create methods for complex scenarios or related records public static Opportunity createOpportunityWithAccount(String oppName, String accName) { Account acc = new Account(Name = accName); insert acc; // Insert the account first Opportunity opp = new Opportunity( Name = oppName, AccountId = acc.Id, StageName = 'Prospecting', CloseDate = Date.today().addDays(30) ); insert opp; // Insert the opportunity return opp; } }
Now, in your actual test classes, you would call these factory methods:
@isTest private class MyApexClassTest { @isTest static void testMyMethodWithSingleRecord() { // Use the factory to create a single account Account testAccount = TestDataFactory.createAccount('My Test Org', 'Finance'); insert testAccount; // Don't forget to insert it! // Now, call the method you are actually testing MyApexClass.someMethod(testAccount.Id); // Add System.asserts to verify results Account updatedAccount = [SELECT Id, My_Custom_Field__c FROM Account WHERE Id = :testAccount.Id]; System.assertEquals('Expected Value', updatedAccount.My_Custom_Field__c); } @isTest static void testMyTriggerWithMultipleRecords() { // Use the factory to create and insert multiple accounts List<Account> testAccounts = TestDataFactory.createAndInsertAccounts(200, 'Bulk Test Account'); // Now, trigger your code (e.g., update these accounts if your trigger is on update) for (Account acc : testAccounts) { acc.Description = 'Updated by test'; } update testAccounts; // This will fire your trigger in bulk // Verify the results List<Account> updatedAccounts = [SELECT Id, Description FROM Account WHERE Id IN :testAccounts]; for (Account acc : updatedAccounts) { System.assertEquals('Updated by test', acc.Description); } } @testSetup // This is even more efficient for common data needed by multiple tests static void setupCommonTestData() { TestDataFactory.createOpportunityWithAccount('Test Opp 1', 'Factory Acc 1'); TestDataFactory.createOpportunityWithAccount('Test Opp 2', 'Factory Acc 2'); // Data created here is available (and rolled back) for each @isTest method. } @isTest static void testLogicUsingSetupData() { // Data from @testSetup is already in the database for this test List<Account> accounts = [SELECT Id, Name FROM Account WHERE Name LIKE 'Factory Acc%' LIMIT 2]; System.assertEquals(2, accounts.size()); // Verify it was created // ... now use these accounts for your test logic } }
22. What’s your process for debugging a tricky bug in production Apex code?
- If the bug is causing big damage, briefly turn off the problematic code (like deactivating a trigger) if it’s safe and quick to do.
- In Salesforce Setup, turn on “Debug Logs” only for the user who gets the bug, and for a short time (like 30 minutes). We can Ask the user to try the action again so we can get a detailed log.
- Open the log in the Developer Console. Look at the steps. Are there too many database queries (SOQL)? Too many updates (DML)? Is memory running out?
- Once we understand the problem from the logs, we can try to make the bug happen in a sandbox. This is where we actually fix it.
- We have to write a new “test method with new system assertions” (Apex test code) that specifically makes the bug happen. Make sure our new code should cover and overal coverage should be more than 75%.
- We have to follow company’s process to move the fixed code from the sandbox’s(QA, UAT) to the live (production) Salesforce system.
23. How do you handle CRUD/FLS in Apex code?
When we write Apex code that works with Salesforce data, we need to be very careful about whether the user running that code actually has permission to do what our code is trying to do. This is where CRUD (Create, Read, Update, Delete) and FLS (Field-Level Security) come in.
CRUD: Checks if the user has permission to do an action on an entire record or object (like create an Account, read a Contact, update an Opportunity, or delete a Lead). We use the Schema class methods to check if a user has permission to read, create, update, or delete records of a specific object.
public class AccountSecurityChecker { public static void checkAccountPermissions() { // Get the SObjectType for Account Schema.SObjectType accountType = Account.SObjectType; // Check if the user can READ (SELECT) Account records if (accountType.getDescribe().isAccessible()) { System.debug('User can READ Account records.'); } else { System.debug('User cannot READ Account records.'); } // Check if the user can CREATE (INSERT) Account records if (accountType.getDescribe().isCreateable()) { System.debug('User can CREATE Account records.'); } else { System.debug('User cannot CREATE Account records.'); } // Check if the user can UPDATE Account records if (accountType.getDescribe().isUpdateable()) { System.debug('User can UPDATE Account records.'); } else { System.debug('User cannot UPDATE Account records.'); } // Check if the user can DELETE Account records if (accountType.getDescribe().isDeletable()) { System.debug('User can DELETE Account records.'); } else { System.debug('User cannot DELETE Account records.'); } } // A more practical example: Protecting an insert operation public static void createAccountSafely(String accountName) { Schema.SObjectType accountType = Account.SObjectType; if (accountType.getDescribe().isCreateable()) { try { Account newAcc = new Account(Name = accountName); insert newAcc; System.debug('Account "' + accountName + '" created successfully.'); } catch (DmlException e) { System.debug('Error creating account: ' + e.getMessage()); } } else { // If the user doesn't have create access for Account throw new AuraHandledException('You do not have permission to create Account records.'); // Or add an error to a page message: ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Permission denied to create Account.')); } } } // Run this in Anonymous Apex (as a user with specific permissions) // AccountSecurityChecker.checkAccountPermissions(); // AccountSecurityChecker.createAccountSafely('My New Safe Account');
FLS: Checks if the user has permission to see or change a specific field on a record (like seeing the “Salary” field on an Employee record, or changing the “Credit Limit” field on an Account). We also use the Schema class methods, but we drill down to specific fields.
public class AccountFieldSecurityChecker { public static void checkAccountFieldPermissions() { // Get the SObjectField for the 'Name' field on Account Schema.SObjectField nameField = Account.Name; // Get the SObjectField for the 'AnnualRevenue' field on Account Schema.SObjectField annualRevenueField = Account.AnnualRevenue; // Check if the user can READ the 'Name' field if (nameField.getDescribe().isAccessible()) { System.debug('User can READ Account.Name.'); } else { System.debug('User cannot READ Account.Name.'); } // Check if the user can UPDATE the 'Name' field if (nameField.getDescribe().isUpdateable()) { System.debug('User can UPDATE Account.Name.'); } else { System.debug('User cannot UPDATE Account.Name.'); } // Check if the user can READ the 'AnnualRevenue' field if (annualRevenueField.getDescribe().isAccessible()) { System.debug('User can READ Account.AnnualRevenue.'); } else { System.debug('User cannot READ Account.AnnualRevenue.'); } // Check if the user can UPDATE the 'AnnualRevenue' field if (annualRevenueField.getDescribe().isUpdateable()) { System.debug('User can UPDATE Account.AnnualRevenue.'); } else { System.debug('User cannot UPDATE Account.AnnualRevenue.'); } } // A more practical example: Protecting an update operation on specific fields public static void updateAccountRevenueSafely(Id accountId, Decimal newRevenue) { Schema.SObjectField annualRevenueField = Account.AnnualRevenue; // First, check if the user can UPDATE the AnnualRevenue field if (annualRevenueField.getDescribe().isUpdateable()) { // Then, also check if the user can access the Account record itself for update // (Often, if you can update a field, you can update the record, but good to be explicit) if (Account.SObjectType.getDescribe().isUpdateable()) { try { Account acc = new Account(Id = accountId, AnnualRevenue = newRevenue); update acc; System.debug('Account ' + accountId + ' AnnualRevenue updated successfully to ' + newRevenue + '.'); } catch (DmlException e) { System.debug('Error updating Account AnnualRevenue: ' + e.getMessage()); } } else { throw new AuraHandledException('You do not have permission to update Account records.'); } } else { // If the user doesn't have FLS permission for AnnualRevenue throw new AuraHandledException('You do not have permission to update the Annual Revenue field.'); } } }
24. How do you enforce Object-Level Security (OLS) when writing SOQL queries in Apex code?.
Enforcing Object-Level Security in SOQL is crucial for ensuring that my Apex code respects the permissions of the user who is running it. By default, Apex runs in ‘System Mode,’ meaning it can access all data regardless of the user’s profile or permission sets. Therefore, we must explicitly tell Apex to enforce security checks.
The primary and most recommended way to achieve Object-Level Security in SOQL is by using the WITH SECURITY_ENFORCED keyword directly in the SOQL query.
try { List<Account> accessibleAccounts = [SELECT Id, Name, AnnualRevenue FROM Account WITH SECURITY_ENFORCED LIMIT 10]; System.debug('Successfully retrieved accounts with enforced security.'); // Proceed with using accessibleAccounts } catch (System.QueryException e) { // Catch the exception if the user lacks object or field permissions System.debug('Security Error: User does not have sufficient permissions to query Accounts or their fields. Message: ' + e.getMessage()); // In a real application, I would throw a user-friendly AuraHandledException or return gracefully. throw new AuraHandledException('You do not have permission to perform this action.'); }
25. Does a System.debug() statement consume CPU time or governor limits?
Yes. While System.debug() is helpful for troubleshooting, too many debug statements can add to CPU time in large loops and affect performance.
26. What is a Mixed DML Operation Error?
A “Mixed DML Operation Error” happens when your Apex code tries to save (insert, update, or delete) two different types of records that belong to different “setup” categories in the same transaction. Salesforce is very strict about this to maintain data integrity and prevent issues with how it saves information behind the scenes.
// Scenario: Trying to create a new User (Setup Object) AND a new Account (Non-Setup Object)
// in the same piece of code.
User newUser = new User(
FirstName = 'Test',
LastName = 'User',
Alias = 'testu',
Email = 'te******@*****le.com',
Username = 'testuser' + System.currentTimeMillis() + '@example.com',
ProfileId = UserInfo.getProfileId(), // Get current user's profile
LocaleSidKey = 'en_US',
EmailEncodingKey = 'UTF-8',
TimeZoneSidKey = 'America/Los_Angeles',
LanguageLocaleKey = 'en_US'
);
insert newUser; // DML on a Setup Object
Account newAccount = new Account(Name = 'New Test Account for User');
insert newAccount; // DML on a Non-Setup Object
// If these two 'insert' statements run one after another in the same transaction,
// this will cause a "Mixed DML Operation Error".
27. What are the Common Exceptions in Apex?
DmlException (Database Save Error): Happens when we try to save, update, or delete records (insert, update, delete commands) and something’s wrong with the data or permissions.
QueryException: Happens when we database query (SELECT or FIND) goes wrong. When query expects exactly one record but finds zero or many, or our query a field/object the user doesn’t have access to when using WITH SECURITY_ENFORCED.
CalloutException: Happens when our Apex code tries to talk to another website or system outside Salesforce, and the connection fails. The external system is down, the internet connection fails, or you forgot to add the website URL to “Remote Site Settings.
NullPointerException: Very common coding mistake. Happens when your code tries to do something with a variable that has no value (it’s null).
LimitException: Happens when your Apex code uses too much of Salesforce’s shared resources, hitting a “governor limit”.
28. What’s the difference between static and transient in Apex? When would you use each?
The static and transient keywords in Apex serve distinct purposes related to how variables behave and persist, but they apply in different contexts. A static variable belongs to the class itself, not to any specific instance or object created from that class. This means there’s only one copy of a static variable available across the entire Apex transaction, and its value persists throughout that transaction, resetting only when a new transaction begins. We would use static for utility methods that don’t need object-specific data, to define global constants, or most critically, to implement trigger recursion control by maintaining a flag that tracks if a trigger’s logic has already executed within the current transaction.
the transient keyword is applied to instance variables and tells Salesforce not to save or serialize that variable’s value when the Apex class instance is stored for later use, most commonly in the Visualforce view state. Variables marked transient are not included when the object is sent between the browser and the server (deserialized). I would primarily use transient in Visualforce controllers to reduce view state size, preventing large datasets or temporary calculations from being carried back and forth with every page request, which can significantly improve page performance. Additionally, it can be used for sensitive data that should not be persisted or for complex objects that are not easily serializable