If you are preparing for a Salesforce Developer Interview in 2025, remember one rule:
👉 Interviewers don’t test your memory — they test your real-time problem-solving.
That’s why I’ve compiled the Top 100 scenario-based Salesforce Developer questions asked in real interviews (TCS, Infosys, Accenture, Wipro, Deloitte, EY, Amex).
Use this as your study + revision guide before your next interview.
🔹 Section 1: Admin & Basics (Q1–Q10)
Q1. Scenario: You need to restrict record access so that Sales Reps can only see their own Opportunities, but Managers can see their team’s Opportunities. How will you achieve this?
👉 Answer:
- Set OWD (Org-Wide Defaults) for Opportunity to Private.
- Enable Role Hierarchy so managers automatically get visibility of their team’s records.
- Assign Sharing Rules if cross-team visibility is needed.
📌 Example:
- Sales Rep John sees only his Opportunities.
- Manager Sarah (above John in Role Hierarchy) automatically sees John’s Opportunities.
Q2. Scenario: A user should be able to edit a record only if a custom checkbox “Editable__c” is TRUE. How will you enforce this?
👉 Answer:
- Use a Validation Rule:
AND(
ISCHANGED(Status__c),
NOT(Editable__c)
)
- This blocks edits unless the checkbox is TRUE.
📌 Why this works: Validation rules are evaluated before save, ensuring no unwanted updates slip through.
Q3. Scenario: You need to send an email alert when a high-value Opportunity is created (> $1M). How will you do this?
👉 Answer:
- Use a Record-Triggered Flow (After Save):
- Entry criteria:
Amount > 1000000
. - Action: Send Email Alert using a pre-configured Email Template.
- Entry criteria:
📌 Best Practice: Don’t use Workflow (deprecated). Flows are future-proof and more powerful.
Q4. Scenario: You need to update a field on Case when related Account field changes. Which automation will you choose?
👉 Answer:
- Use After-Save Record-Triggered Flow on Account:
- When Account field (e.g., Status) changes,
- Query related Cases,
- Update Case fields with
Update Records
element.
📌 Why Flow instead of Trigger?
- Easy to maintain, low code, no deployment needed.
Q5. Scenario: You need to mass-update 100,000 records. Which tool will you use?
👉 Answer:
- If it’s a one-time update → Use Data Loader.
- If it’s recurring (daily, weekly) → Use Batch Apex.
📌 Code Example (Batch Apex)
global class UpdateBatch implements Database.Batchable<sObject> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator('SELECT Id FROM Account WHERE Status__c = \'Active\'');
}
global void execute(Database.BatchableContext bc, List<Account> accList) {
for(Account acc : accList){
acc.Rating = 'Hot';
}
update accList;
}
global void finish(Database.BatchableContext bc) {}
}
Q6. Scenario: You need to assign a task automatically when a new Lead is created.
👉 Answer:
- Use After-Save Flow on Lead.
- Action: Create Task record with subject “Follow-up Lead”.
📌 Why not Process Builder?
- Process Builder is deprecated; Flows replace it.
Q7. Scenario: Business wants to auto-assign Accounts to different queues based on Region.
👉 Answer:
- Create Queues for each Region.
- Use Record-Triggered Flow with Decision Element:
- If Region = “East” → Assign to East Queue.
- If Region = “West” → Assign to West Queue.
📌 Pro Tip: Assignment Rules can also be used for Leads/Cases, but for Account you’ll need Flow.
Q8. Scenario: You need to track changes in a field (audit log).
👉 Answer:
Options:
- Field History Tracking (up to 20 fields per object).
- Field Audit Trail (FAT) (if Shield license available) for long-term tracking.
📌 Example: Track StageName
changes in Opportunity to measure sales cycle.
Q9. Scenario: You need to restrict a picklist to only a few values for certain profiles.
👉 Answer:
- Use Record Types with different Picklist value sets.
- Assign Record Types to Profiles.
📌 Example:
- Sales User sees {“Prospect”, “Negotiation”}.
- Manager sees {“Prospect”, “Negotiation”, “Closed Won”}.
Q10. Scenario: Two users need different page layouts for the same object.
👉 Answer:
- Create two Record Types → Assign different Page Layouts per Profile.
📌 Example:
- Customer Support rep sees only Case basic info.
- Manager sees Case info + SLA + Internal Notes.
🔹 Section 2: Apex & Trigger Scenarios (Q11–Q30)
Q11. Scenario: Insert Account & User in same transaction → Mix DML error. How do you fix?
👉 Answer:
- Salesforce doesn’t allow Setup (User) and Non-Setup (Account) object DML in the same transaction → Mix DML error.
- Fix: Use Async Apex (Future/Queueable) for User creation.
📌 Code Example:
public class UserHandler {
@future
public static void createUser(Id accId) {
Account acc = [SELECT Name FROM Account WHERE Id = :accId LIMIT 1];
User u = new User(
FirstName = 'Test',
LastName = acc.Name,
Alias = 'tuser',
Email = 'test@test.com',
Username = 'testuser@test.com',
ProfileId = [SELECT Id FROM Profile WHERE Name='Standard User' LIMIT 1].Id,
TimeZoneSidKey = 'Asia/Kolkata',
LocaleSidKey = 'en_US',
EmailEncodingKey='UTF-8',
LanguageLocaleKey='en_US'
);
insert u;
}
}
Q12. Scenario: You need to stop users from deleting Accounts if related Opportunities exist.
👉 Answer:
- Use
before delete
Trigger → check related Opportunities. - If found, block with
addError()
.
📌 Code Example:
trigger AccountBeforeDelete on Account (before delete) {
for(Account acc : Trigger.old){
if([SELECT COUNT() FROM Opportunity WHERE AccountId = :acc.Id] > 0){
acc.addError('Cannot delete Account with related Opportunities.');
}
}
}
Q13. Scenario: When Account Industry changes, update all child Contacts with the same Industry.
👉 Answer:
- Use
after update
Trigger on Account.
📌 Code Example:
trigger UpdateContactsIndustry on Account (after update) {
List<Contact> consToUpdate = new List<Contact>();
for(Account acc : Trigger.new){
Account oldAcc = Trigger.oldMap.get(acc.Id);
if(acc.Industry != oldAcc.Industry){
for(Contact con : [SELECT Id FROM Contact WHERE AccountId=:acc.Id]){
con.Industry__c = acc.Industry;
consToUpdate.add(con);
}
}
}
if(!consToUpdate.isEmpty()) update consToUpdate;
}
Q14. Scenario: Prevent duplicate Contacts with same Email.
👉 Answer:
- Use Trigger + Set for Email uniqueness.
trigger ContactDuplicateCheck on Contact (before insert, before update) {
Set<String> emailSet = new Set<String>();
for(Contact c : Trigger.new){
if(c.Email != null) emailSet.add(c.Email);
}
Map<String, Contact> dupMap = new Map<String, Contact>();
for(Contact existing : [SELECT Email FROM Contact WHERE Email IN :emailSet]){
dupMap.put(existing.Email, existing);
}
for(Contact c : Trigger.new){
if(dupMap.containsKey(c.Email)){
c.addError('Duplicate Email Found!');
}
}
}
Q15. Scenario: Trigger should run only once, even if multiple DMLs occur.
👉 Answer:
- Use a Static Boolean flag.
public class TriggerHelper {
public static Boolean firstRun = true;
}
trigger AccountTrigger on Account (after insert) {
if(TriggerHelper.firstRun){
TriggerHelper.firstRun = false;
// logic here
}
}
Q16. Scenario: You need to handle bulk insert of 200 Accounts and update related Contacts.
👉 Answer:
- Use Maps for bulk-safe processing.
trigger AccountBulkHandler on Account (after insert) {
Map<Id, Account> accMap = new Map<Id, Account>(Trigger.new);
List<Contact> cons = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accMap.keySet()];
for(Contact c : cons){
c.Description = 'Updated via bulk insert';
}
update cons;
}
Q17. Scenario: Need to log deleted records for auditing.
👉 Answer:
- Use
after delete
trigger withTrigger.old
.
trigger AccountDeleteAudit on Account (after delete) {
List<Audit__c> audits = new List<Audit__c>();
for(Account acc : Trigger.old){
audits.add(new Audit__c(Name='Deleted '+acc.Name, AccountId__c=acc.Id));
}
insert audits;
}
Q18. Scenario: You must prevent recursion in Trigger calling a Flow.
👉 Answer:
- Use static variable flag before calling Flow.
if(TriggerHelper.firstRun){
TriggerHelper.firstRun = false;
Flow.Interview.MyFlow flow = new Flow.Interview.MyFlow(vars);
flow.start();
}
Q19. Scenario: Trigger should update related records but skip if update is already from automation.
👉 Answer:
- Add custom flag field
Updated_By_Flow__c
to differentiate. - In Trigger, check if flag is false before proceeding.
Q20. Scenario: Insert Case when Account Type = “Customer”.
👉 Answer:
trigger CreateCaseOnAccount on Account (after insert) {
List<Case> cases = new List<Case>();
for(Account acc : Trigger.new){
if(acc.Type=='Customer'){
cases.add(new Case(Subject='New Customer Case', AccountId=acc.Id));
}
}
if(!cases.isEmpty()) insert cases;
}
Q21. Scenario: Update Account Rating when 5+ Opportunities are Closed Won.
👉 Answer:
trigger UpdateAccountRating on Opportunity (after insert, after update) {
Map<Id, Integer> accOppCount = new Map<Id, Integer>();
for(AggregateResult ar : [
SELECT AccountId accId, COUNT(Id) cnt
FROM Opportunity
WHERE StageName='Closed Won'
GROUP BY AccountId
]){
accOppCount.put((Id)ar.get('accId'), (Integer)ar.get('cnt'));
}
List<Account> accToUpdate = new List<Account>();
for(Id accId : accOppCount.keySet()){
if(accOppCount.get(accId) >= 5){
accToUpdate.add(new Account(Id=accId, Rating='Hot'));
}
}
update accToUpdate;
}
Q22. Scenario: Assign default Contact when new Account created.
👉 Answer:
trigger DefaultContact on Account (after insert) {
List<Contact> cons = new List<Contact>();
for(Account acc : Trigger.new){
cons.add(new Contact(LastName='Default Contact', AccountId=acc.Id));
}
insert cons;
}
Q23. Scenario: Prevent Opportunity close if Quote not approved.
👉 Answer:
trigger OppStageCheck on Opportunity (before update) {
for(Opportunity opp : Trigger.new){
if(opp.StageName=='Closed Won'){
Integer quoteCount = [SELECT COUNT() FROM Quote WHERE OpportunityId=:opp.Id AND Status='Approved'];
if(quoteCount==0){
opp.addError('Cannot close Opportunity until Quote approved.');
}
}
}
}
Q24. Scenario: Auto-create Renewal Opportunity when current Opp is Closed Won.
👉 Answer:
trigger RenewalOpp on Opportunity (after update) {
List<Opportunity> renewals = new List<Opportunity>();
for(Opportunity opp : Trigger.new){
if(opp.StageName=='Closed Won' && Trigger.oldMap.get(opp.Id).StageName!='Closed Won'){
Opportunity newOpp = opp.clone(false,true);
newOpp.StageName = 'Prospecting';
newOpp.CloseDate = System.today().addMonths(12);
renewals.add(newOpp);
}
}
insert renewals;
}
Q25. Scenario: Prevent delete of Product if used in Opportunity Line Item.
👉 Answer:
trigger PreventProductDelete on Product2 (before delete) {
for(Product2 p : Trigger.old){
if([SELECT COUNT() FROM OpportunityLineItem WHERE Product2Id=:p.Id] > 0){
p.addError('Cannot delete Product as it is used in Opportunity Line Items.');
}
}
}
Q26. Scenario: You need to bulk update 10k Accounts → CPU limit hit.
👉 Answer:
- Solution → Batch Apex to process records in chunks.
- Also use Maps, Aggregates, Set operations.
Q27. Scenario: Calculate average discount across Opportunity Line Items.
👉 Answer:
AggregateResult[] results = [
SELECT OpportunityId, AVG(Discount__c) avgDiscount
FROM OpportunityLineItem
GROUP BY OpportunityId
];
Q28. Scenario: Stop users from updating Opportunity Stage backward.
👉 Answer:
trigger PreventStageBackwards on Opportunity (before update) {
for(Opportunity opp : Trigger.new){
Opportunity oldOpp = Trigger.oldMap.get(opp.Id);
if(oldOpp.StageName=='Closed Won' && opp.StageName!='Closed Won'){
opp.addError('Stage cannot move backwards.');
}
}
}
Q29. Scenario: Ensure Account Name always starts with Region Code.
👉 Answer:
trigger AccountNameCheck on Account (before insert, before update) {
for(Account acc : Trigger.new){
if(!acc.Name.startsWith(acc.Region__c)){
acc.Name = acc.Region__c + '-' + acc.Name;
}
}
}
Q30. Scenario: Cascade delete related records when Parent custom object deleted.
👉 Answer:
- Use
after delete
trigger.
trigger ParentDelete on Parent__c (after delete) {
delete [SELECT Id FROM Child__c WHERE Parent__c IN :Trigger.oldMap.keySet()];
}
🔹 Section 3: SOQL & Database (Q31–Q50)
Q31. Scenario: Fetch Accounts with no Contacts.
Answer: Use an anti-join (semi-join with NOT IN
) to find parents without children.
SOQL
SELECT Id, Name
FROM Account
WHERE Id NOT IN (SELECT AccountId FROM Contact)
Apex (render list)
List<Account> noContactAccs = [
SELECT Id, Name
FROM Account
WHERE Id NOT IN (SELECT AccountId FROM Contact)
LIMIT 5000
];
Best practices
- Anti-joins are efficient and governor-friendly.
- Add filters (e.g., Active, Region) to keep queries selective.
Q32. Scenario: Get Opportunity counts by Stage for each Account (for a dashboard).
Answer: Use aggregate SOQL and group by both AccountId
and StageName
.
SOQL
SELECT AccountId, StageName, COUNT(Id) cnt
FROM Opportunity
WHERE IsClosed = FALSE
GROUP BY AccountId, StageName
Apex (convert to a nested map)
Map<Id, Map<String, Integer>> byAccStage = new Map<Id, Map<String, Integer>>();
for (AggregateResult ar : [
SELECT AccountId acc, StageName stg, COUNT(Id) cnt
FROM Opportunity
WHERE IsClosed = FALSE
GROUP BY AccountId, StageName
]) {
Id accId = (Id) ar.get('acc');
String stage = (String) ar.get('stg');
Integer count = (Integer) ar.get('cnt');
byAccStage.putIfAbsent(accId, new Map<String, Integer>());
byAccStage.get(accId).put(stage, count);
}
Best practices
- Prefer aggregate queries over looping + counting in Apex.
Q33. Scenario: Prevent concurrent updates — lock records before edit.
Answer: Use FOR UPDATE
to get a row lock.
Apex
List<Account> accs = [
SELECT Id, Name
FROM Account
WHERE Id IN :accIds
FOR UPDATE
];
// safe to modify now
for (Account a : accs) a.Rating = 'Hot';
update accs;
Best practices
- Use in short, focused transactions to avoid lock contention.
- Catch and handle
UNABLE_TO_LOCK_ROW
errors (e.g., show a friendly message, retry via async).
Q34. Scenario: Top 5 Accounts by AnnualRevenue.
Answer: Sort + limit.
SELECT Id, Name, AnnualRevenue
FROM Account
WHERE AnnualRevenue != NULL
ORDER BY AnnualRevenue DESC
LIMIT 5
Tip: Use a selective WHERE to avoid scanning huge tables.
Q35. Scenario: Find duplicate Contacts by Email.
Answer: Group and HAVING.
SELECT Email, COUNT(Id) dupCount
FROM Contact
WHERE Email != NULL
GROUP BY Email
HAVING COUNT(Id) > 1
Apex (flag dupes)
Set<String> dupEmails = new Set<String>();
for (AggregateResult ar : [
SELECT Email em, COUNT(Id) c FROM Contact
WHERE Email != NULL GROUP BY Email HAVING COUNT(Id) > 1
]) dupEmails.add((String) ar.get('em'));
Best practices
- Follow up with merge strategy or duplicate rules for prevention.
Q36. Scenario: Report Opportunities closed last month.
Answer: Use date literals (fast & index-friendly).
SELECT Id, Name, Amount, CloseDate
FROM Opportunity
WHERE IsClosed = TRUE
AND CloseDate = LAST_MONTH
Tip: Date literals (e.g., YESTERDAY
, LAST_N_DAYS:30
, THIS_FISCAL_QUARTER
) are optimized.
Q37. Scenario: Need parent Account fields while querying Contacts.
Answer: Child-to-parent dot notation.
SELECT Id, LastName, Email, Account.Name, Account.Industry, Account.Owner.Name
FROM Contact
WHERE Account.Industry = 'Banking'
Best practices
- Fetch only the parent fields you need to stay within query row/heap limits.
Q38. Scenario: Fetch Accounts with all their Contacts in one query.
Answer: Parent-to-child subquery.
SELECT Id, Name,
(SELECT Id, LastName, Email FROM Contacts)
FROM Account
WHERE Rating = 'Hot'
Apex
for (Account a : [
SELECT Id, Name, (SELECT Id, LastName, Email FROM Contacts)
FROM Account WHERE Rating = 'Hot'
]) {
for (Contact c : a.Contacts) {
// process
}
}
Note: Subqueries return a QueryResult list on the relationship name.
Q39. Scenario: Large table is slow — make the query selective.
Answer: Use indexed fields and sargable predicates.
Do this
- Filter on Id, OwnerId, CreatedDate, External Ids, Marked-as-Indexed custom fields.
- Use prefix matches (
LIKE :term + '%'
) instead of%term%
.
Example
SELECT Id, Name
FROM Account
WHERE IsActive__c = TRUE
AND Name LIKE :namePrefix // 'Acme%'
AND LastModifiedDate = LAST_N_DAYS:30
Best practices
- Check the Query Plan tool to confirm cost < 1 (selective).
- Consider archiving old data to reduce working set.
Q40. Scenario: Only Accounts that have Opportunities.
Answer: Semi-join with IN
.
SELECT Id, Name
FROM Account
WHERE Id IN (SELECT AccountId FROM Opportunity WHERE IsClosed = FALSE)
Tip: Semi-joins are very efficient versus doing two queries and intersecting in Apex.
Q41. Scenario: Accounts that have no open Opportunities (but may have closed ones).
Answer: Use an anti-join with a filtered subquery.
SELECT Id, Name
FROM Account
WHERE Id NOT IN (
SELECT AccountId FROM Opportunity WHERE IsClosed = FALSE
)
Best practices
- Keep the subquery selective (add filters like region, type).
Q42. Scenario: Paginate thousands of rows in a UI list.
Answer: Prefer keyset pagination over OFFSET
(which is capped ~2K and slower).
Keyset approach
// First page
List<Account> page1 = [
SELECT Id, Name FROM Account
WHERE Name >= :startKey
ORDER BY Name ASC
LIMIT :pageSize
];
// Next page: pass last record’s Name as new startKey
Best practices
- Sort by an indexed, unique-ish field.
- Store the last key (Id/Name) per page to fetch the next slice.
Q43. Scenario: Filter children by parent fields (e.g., Contacts where Account = Banking).
Answer: Put the parent predicate in the WHERE via dot notation.
SELECT Id, LastName, Email
FROM Contact
WHERE Account.Industry = 'Banking'
AND Account.Active__c = TRUE
Tip: This avoids an extra query and keeps logic declarative.
Q44. Scenario: Avoid SOQL-in-loop when enriching records.
Answer: First gather keys, then bulk query once, then map.
Bad (don’t do)
for (Contact c : Trigger.new) {
Account a = [SELECT Name FROM Account WHERE Id = :c.AccountId]; // in loop ❌
}
Good
Set<Id> accIds = new Set<Id>();
for (Contact c : Trigger.new) if (c.AccountId != null) accIds.add(c.AccountId);
Map<Id, Account> accMap = new Map<Id, Account>([
SELECT Id, Name FROM Account WHERE Id IN :accIds
]);
for (Contact c : Trigger.new) {
Account a = accMap.get(c.AccountId);
// use a safely
}
Q45. Scenario: Search a keyword across Account, Contact, and Opportunity names.
Answer: Use SOSL for full-text, multi-object search.
List<List<SObject>> sr = [
FIND :searchTerm IN NAME FIELDS
RETURNING
Account(Id, Name ORDER BY Name LIMIT 10),
Contact(Id, Name, Email ORDER BY Name LIMIT 10),
Opportunity(Id, Name ORDER BY Name LIMIT 10)
];
List<Account> accs = (List<Account>) sr[0];
Best practices
- Use IN ALL FIELDS if you need description/notes too (heavier).
- Use LIMIT per object.
Q46. Scenario: Upsert external data using External Id.
Answer: Mark a field as External Id and use upsert
.
List<Product2> items = new List<Product2>{
new Product2(SKU__c='SKU-1001', Name='Cable'),
new Product2(SKU__c='SKU-1002', Name='Adapter')
};
Database.UpsertResult[] res = Database.upsert(items, Product2.SKU__c, false);
Best practices
- External Id avoids duplicate inserts.
- Set allOrNone=false for partial success.
Q47. Scenario: Handle partial DML success and collect errors.
Answer: Use Database.insert
/update
with allOrNone=false
.
Database.SaveResult[] results = Database.insert(records, /* allOrNone */ false);
for (Integer i=0; i<results.size(); i++) {
if (!results[i].isSuccess()) {
for (Database.Error e : results[i].getErrors()) {
System.debug('Row ' + i + ' failed: ' + e.getMessage());
}
}
}
Tip: Always log failed Ids/payloads for supportability.
Q48. Scenario: Querying > 50k rows safely.
Answer: Use Batch Apex with a QueryLocator (streams server-side).
global class BigQueryBatch implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id FROM Contact WHERE LastModifiedDate = LAST_N_DAYS:365'
);
}
global void execute(Database.BatchableContext bc, List<Contact> scope) {
// process scope (<= 200 each)
}
global void finish(Database.BatchableContext bc) {}
}
Best practices
- Avoid pulling huge lists into a single transaction (heap/governor limits).
Q49. Scenario: Record data skew (1 owner with millions of children) causing locks/timeouts.
Answer: Mitigate ownership & lookup skew.
What to do
- Distribute ownership across users/queues (avoid “hot” owners).
- Turn on Deferred Sharing Calculation during big loads.
- Avoid heavy triggers on skewed parents; push to async where possible.
Detection
- Look for many children with the same Lookup/Owner and frequent updates.
Q50. Scenario: “Non-selective query against large object” error in triggers.
Answer: Make the query selective or move to async.
Fixes
- Filter on indexed fields (e.g.,
IsActive__c
,CreatedDate
,External Id
). - Add additional predicates to reduce cardinality.
- If still heavy, run logic via Batch/Queueable triggered by a lightweight flag.
Example (selective)
SELECT Id FROM Case
WHERE Status = 'New'
AND Priority IN ('High','Critical')
AND CreatedDate = LAST_N_DAYS:7
🔹 Section 4: Lightning Web Components (Q51–Q60)
Q51. Scenario: Refresh LWC data after a record update (no page reload).
Answer: Use a wired Apex/Ui API adapter and call refreshApex
after DML or a modal save.
Code (Apex)
public with sharing class AccountCtrl {
@AuraEnabled(cacheable=true)
public static List<Account> getHotAccounts() {
return [SELECT Id, Name, Rating FROM Account WHERE Rating = 'Hot' ORDER BY Name LIMIT 100];
}
}
Code (LWC JS)
import { LightningElement, wire, track } from 'lwc';
import getHotAccounts from '@salesforce/apex/AccountCtrl.getHotAccounts';
import { refreshApex } from '@salesforce/apex';
export default class HotAccountList extends LightningElement {
@track rows = [];
wiredResult;
@wire(getHotAccounts)
wiredAccs(result) {
this.wiredResult = result;
if (result.data) this.rows = result.data;
}
async handleSaved() {
await refreshApex(this.wiredResult);
}
}
Code (LWC HTML)
<template>
<lightning-button label="Refresh" onclick={handleSaved}></lightning-button>
<template if:true={rows}>
<template for:each={rows} for:item="r">
<p key={r.Id}>{r.Name} — {r.Rating}</p>
</template>
</template>
</template>
Best practices
- Always keep a handle to the wired result.
- For Ui API (
getRecord
,getListUi
), also userefreshApex
afterupdateRecord
.
Q52. Scenario: Build efficient, scalable pagination for large lists.
Answer: Prefer keyset pagination (cursor-based) over OFFSET
. Pass the last key to Apex and fetch the next slice.
Code (Apex)
public with sharing class AccountPageCtrl {
@AuraEnabled(cacheable=true)
public static List<Account> pageByName(String lastNameKey, Integer pageSize) {
String key = String.isBlank(lastNameKey) ? '' : lastNameKey;
return [
SELECT Id, Name, Phone
FROM Account
WHERE Name > :key
ORDER BY Name ASC
LIMIT :pageSize
];
}
}
Code (LWC JS)
import { LightningElement, track } from 'lwc';
import pageByName from '@salesforce/apex/AccountPageCtrl.pageByName';
export default class AccountPager extends LightningElement {
@track rows = [];
lastKey = '';
pageSize = 20;
hasMore = true;
connectedCallback() {
this.loadMore();
}
async loadMore() {
if (!this.hasMore) return;
const data = await pageByName({ lastNameKey: this.lastKey, pageSize: this.pageSize });
this.rows = [...this.rows, ...data];
this.lastKey = data.length ? data[data.length - 1].Name : this.lastKey;
this.hasMore = data.length === this.pageSize;
}
}
Best practices
- Use indexed sort columns (Name, Id, CreatedDate).
- Keep pages small (20–50 rows) to avoid UI jank.
Q53. Scenario: Show a dynamic picklist from metadata (dependent on Record Type).
Answer: Use Ui Object Info API: getObjectInfo
→ get defaultRecordTypeId
, then getPicklistValues
.
Code (LWC JS)
import { LightningElement, wire, track } from 'lwc';
import { getObjectInfo, getPicklistValues } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
export default class IndustryPicklist extends LightningElement {
@track options = [];
recordTypeId;
@wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
objInfo({ data }) {
if (data) this.recordTypeId = data.defaultRecordTypeId;
}
@wire(getPicklistValues, { recordTypeId: '$recordTypeId', fieldApiName: INDUSTRY_FIELD })
wiredPicklist({ data }) {
if (data) this.options = data.values.map(v => ({ label: v.label, value: v.value }));
}
}
Code (LWC HTML)
<template>
<lightning-combobox name="industry" label="Industry" options={options}></lightning-combobox>
</template>
Best practices
- Cache results (wire is cacheable).
- For dependent picklists, also wire the controlling field.
Q54. Scenario: Share state across unrelated components (same page, no relation).
Answer: Use Lightning Message Service (LMS) with a custom message channel.
Metadata — messageChannels/AppBus.messageChannel-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>AppBus</masterLabel>
<isExposed>true</isExposed>
<description>Cross-component bus</description>
<lightningMessageFields>
<fieldName>recordId</fieldName>
<description>Selected record Id</description>
</lightningMessageFields>
</LightningMessageChannel>
Publisher (LWC JS)
import { LightningElement, wire } from 'lwc';
import { MessageContext, publish } from 'lightning/messageService';
import APP_BUS from '@salesforce/messageChannel/AppBus__c';
export default class Publisher extends LightningElement {
@wire(MessageContext) context;
notifySelect(event) {
publish(this.context, APP_BUS, { recordId: event.detail.id });
}
}
Subscriber (LWC JS)
import { LightningElement, wire } from 'lwc';
import { MessageContext, subscribe, APPLICATION_SCOPE } from 'lightning/messageService';
import APP_BUS from '@salesforce/messageChannel/AppBus__c';
export default class Subscriber extends LightningElement {
@wire(MessageContext) context;
subscription;
connectedCallback() {
this.subscription = subscribe(
this.context,
APP_BUS,
(message) => this.handle(message),
{ scope: APPLICATION_SCOPE }
);
}
handle(message) {
// use message.recordId
}
}
Best practices
- Unsubscribe in
disconnectedCallback
if you create custom subscriptions. - Use APPLICATION_SCOPE to communicate across the page.
Q55. Scenario: Show a loading spinner while calling Apex.
Answer: Toggle an isLoading
flag; show <lightning-spinner>
until promise resolves.
Code (LWC JS)
import { LightningElement, track } from 'lwc';
import doWork from '@salesforce/apex/HeavyCtrl.doWork';
export default class SpinnerDemo extends LightningElement {
@track isLoading = false;
async run() {
try {
this.isLoading = true;
await doWork();
} finally {
this.isLoading = false;
}
}
}
Code (LWC HTML)
<template>
<lightning-button label="Run" onclick={run}></lightning-button>
<template if:true={isLoading}>
<lightning-spinner alternative-text="Loading"></lightning-spinner>
</template>
</template>
Best practices
- Always clear spinners in
finally
to avoid stuck UI.
Q56. Scenario: Use a Static Resource (CSS/JS) in LWC.
Answer: Import the resource with @salesforce/resourceUrl
and load with loadStyle
/loadScript
.
Code (LWC JS)
import { LightningElement } from 'lwc';
import { loadStyle, loadScript } from 'lightning/platformResourceLoader';
import APP_CSS from '@salesforce/resourceUrl/appStyles';
import CHARTJS from '@salesforce/resourceUrl/chartjs'; // zipped & namespaced
export default class StaticResDemo extends LightningElement {
rendered = false;
renderedCallback() {
if (this.rendered) return;
this.rendered = true;
Promise.all([
loadStyle(this, APP_CSS),
loadScript(this, CHARTJS + '/chart.umd.js')
]).catch(err => { /* handle */ });
}
}
Best practices
- Guard with a rendered flag to avoid duplicate loads.
- Package JS libraries inside a zip and refer to the file path.
Q57. Scenario: Build a reusable “Card” component for consistent UI.
Answer: Expose @api
props and slots. Consumers pass content; the card keeps styling.
Reusable Card (LWC HTML)
<template>
<section class="card">
<header class="head">
<h2>{title}</h2>
<slot name="actions"></slot>
</header>
<div class="body">
<slot></slot>
</div>
</section>
</template>
Reusable Card (LWC JS)
import { LightningElement, api } from 'lwc';
export default class UiCard extends LightningElement {
@api title = 'Card';
}
Consumer
<c-ui-card title="Hot Accounts">
<lightning-button slot="actions" label="New"></lightning-button>
<ul>
<template for:each={rows} for:item="r">
<li key={r.Id}>{r.Name}</li>
</template>
</ul>
</c-ui-card>
Best practices
- Use slots for flexible content injection.
- Keep the card logic-free so it stays reusable.
Q58. Scenario: Style isolation & themes without leaking CSS.
Answer: Use scoped CSS (.css
next to the component). For theming, use :host
and variants.
CSS
:host {
display: block;
}
:host([variant="brand"]) .btn {
font-weight: 600;
}
.btn {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
}
HTML
<template>
<button class="btn">Click</button>
</template>
Best practices
- LWC CSS is shadow-DOM scoped (won’t affect other components).
- To modify SLDS tokens, prefer Design Tokens instead of hardcoded colors.
Q59. Scenario: Load a third-party JS library once and render a chart.
Answer: Guard loading with a flag; then initialize the library.
Code (LWC JS)
import { LightningElement } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import CHARTJS from '@salesforce/resourceUrl/chartjs';
export default class ChartDemo extends LightningElement {
isLibReady = false;
chart;
async renderedCallback() {
if (this.isLibReady) return;
await loadScript(this, CHARTJS + '/chart.umd.js');
this.isLibReady = true;
this.initChart();
}
initChart() {
const ctx = this.template.querySelector('canvas').getContext('2d');
this.chart = new window.Chart(ctx, {
type: 'bar',
data: { labels: ['A','B','C'], datasets: [{ data: [3,7,5] }] },
options: { responsive: true }
});
}
}
HTML
<template>
<canvas style="max-width:600px;"></canvas>
</template>
Best practices
- Avoid re-initializing on every render; cache the library load.
Q60. Scenario: Create/Edit a record without Apex (Ui Record API).
Answer: Use <lightning-record-form>
(quickest) or createRecord/updateRecord
for custom UX.
Quick form (HTML)
<template>
<lightning-record-form
record-id={recordId}
object-api-name="Account"
layout-type="Full"
mode="edit"
onsuccess={handleSuccess}>
</lightning-record-form>
</template>
Custom save (JS)
import { LightningElement, api } from 'lwc';
import { updateRecord } from 'lightning/uiRecordApi';
import NAME from '@salesforce/schema/Account.Name';
import ID from '@salesforce/schema/Account.Id';
export default class UiSaveDemo extends LightningElement {
@api recordId;
async save(name) {
const fields = {};
fields[ID.fieldApiName] = this.recordId;
fields[NAME.fieldApiName] = name;
await updateRecord({ fields });
// toast + refreshApex wired adapters if any
}
}
Best practices
- Prefer Ui API over Apex for basic CRUD (honors FLS/CRUD automatically).
- Use
onsuccess
to show toast and triggerrefreshApex
.
🔹 Section 5: Flows & Automation (Q61–Q75)
Q61. Scenario: Auto-update all child Contacts’ Phone when Account Phone changes.
Answer: Use an After-Save Record-Triggered Flow on Account.
Design
- Trigger: Account → When record is updated.
- Entry criteria:
ISCHANGED({!$Record.Phone})
andNOT(ISBLANK({!$Record.Phone}))
. - Get Records: Contacts where
AccountId = {!$Record.Id}
. - Update Records: Set
Phone = {!$Record.Phone}
for all in the collection.
Best practices
- Use After-Save for related DML.
- Avoid Get if you can use Update Related Records (newer Flow builder feature).
Q62. Scenario: Create a Task automatically when Opportunity becomes Closed Won.
Answer: After-Save Flow on Opportunity.
Design
- Entry:
ISCHANGED(StageName) && StageName = 'Closed Won'
. - Action: Create Records → Task
- Subject: “Customer Onboarding”
- WhatId:
{$Record.Id}
- OwnerId:
{$Record.OwnerId}
- Priority: High
Best practices
- Use isChanged to avoid duplicate tasks.
- Prefer Text Templates for consistent Subjects.
Q63. Scenario: Collect additional info from user before creating Case.
Answer: Screen Flow launched from a button/quick action.
Design
- Screen 1: Inputs (Category, SLA, Description).
- Create Records: Case with the captured fields.
- Navigate to the created Case with a Finish redirect.
Best practices
- Validate on-screen with Component visibility and Input validation.
- Use Dynamic Forms on record pages where possible; Screen Flow for multi-step UX.
Q64. Scenario: Retry an HTTP callout if it fails (from Flow).
Answer: Use Flow → Invocable Apex that performs the callout and returns success status; implement retry in Apex or via Flow loop with a counter.
Invocable Apex (Named Credential recommended)
public with sharing class FxRateAction {
public class Request { @InvocableVariable public String base; public String target; }
public class Response { @InvocableVariable public Boolean success; @InvocableVariable public Decimal rate; @InvocableVariable public String message; }
@InvocableMethod(label='Get FX Rate' callout=true)
public static List<Response> getRate(List<Request> reqs){
List<Response> out = new List<Response>();
for (Request r : reqs) {
try {
HttpRequest h = new HttpRequest();
h.setEndpoint('callout:FX_API/rate?base=' + r.base + '&target=' + r.target);
h.setMethod('GET');
Http http = new Http();
HttpResponse res = http.send(h);
if (res.getStatusCode()==200) {
// parse JSON (pseudo)
Decimal rate = (Decimal) JSON.deserializeUntyped(res.getBody()).get('rate');
out.add(new Response(true, rate, 'OK'));
} else out.add(new Response(false, null, 'HTTP ' + res.getStatus()));
} catch (Exception e) {
out.add(new Response(false, null, e.getMessage()));
}
}
return out;
}
}
Flow
- Action: Get FX Rate
- Decision: If
success = false
andattempts < 3
→ wait Scheduled Path (e.g., 5 minutes) → retry.
Best practices
- Use Named Credentials; never hardcode URLs/keys.
- Expose a status + message back to Flow for fault handling.
Q65. Scenario: Mass update related child records (no Apex).
Answer: After-Save Flow with Collection Update.
Design
- Trigger on Parent (e.g., Account): if
Tier__c
changed - Get Records: Contacts under the account
- Update Records → Use IDs from record collection: set
Tier__c = {!$Record.Tier__c}
Best practices
- Keep Get Records unscoped but filtered by relationship Id to stay selective.
- For >2k children, consider Batch Apex.
Q66. Scenario: Auto-assign Case to a queue based on Priority.
Answer: Record-Triggered Flow on Case (Before-Save).
Design
- Entry: when created/edited.
- Decision:
- If
Priority = 'High'
→OwnerId = {!$Record.Queue_High_Id__c}
- Else if
Priority = 'Medium'
→ set to medium queue
- If
- Because it’s Before-Save, you can just assign OwnerId without extra DML.
Best practices
- Before-Save is 10× faster for field updates.
- Use Custom Metadata to map Priority → Queue (no hardcoding).
Q67. Scenario: Nightly mass update (e.g., recalculate Health Score).
Answer: Scheduled-Triggered Flow (or Scheduled Path on an object if event-driven).
Design
- Flow Type: Scheduled-Triggered
- Frequency: Daily 02:00 AM IST
- Query: Accounts updated in last 30 days
- Loop & Assignment: recompute
Health_Score__c
- Update Records (collection)
Best practices
- Keep each run under limits. If large, prefer Batch Apex.
- Add “Last Scored On” to avoid reprocessing too often.
Q68. Scenario: Reuse a common sub-process in multiple flows.
Answer: Build a Subflow and call it from parent flows.
Design
- Subflow “Normalize Phone”: takes a phone string, returns formatted E.164.
- Parent flows call Subflow wherever phone is entered/changed.
Best practices
- Centralize logic → easier maintenance.
- Version your Subflows; test independently.
Q69. Scenario: Graceful error handling + user-friendly message.
Answer: Use Fault Paths on Actions/Get/Update elements and store error text in a variable; show via Screen (for Screen Flows) or Post a Platform Notification/Email (for background flows).
Pattern
- For each element with a fault path → Assignment:
varErrorMsg = FLOW_FAULT_MESSAGE
. - Then Action: Send Email/Slack with the error and input context.
Best practices
- Never leave fault paths unconnected; operationalize errors.
Q70. Scenario: Notify Sales Manager on “High-Risk” Account updates.
Answer: After-Save Flow on Account → Send Email or Post to Slack via Invocable Action.
Design
- Criteria:
ISCHANGED(Risk__c) && Risk__c = 'High'
- Action: Email Alert to Manager; or custom Slack Invocable.
Best practices
- For external tools, prefer Platform Events or Invocable with retry/error handling.
Q71. Scenario: Create a Contact automatically when a Lead converts with missing Contact.
Answer: Record-Triggered Flow on Lead with condition “When Converted = TRUE”.
Design
- Trigger: After-Save on Lead
- Decision: If
IsConverted = TRUE
andConvertedContactId = null
- Create Records: Contact with fields mapped from Lead
Best practices
- Be careful with Lead conversion timing; use After-Save and check conversion Ids.
Q72. Scenario: Call an external API directly from Flow for KYC validation.
Answer: Use Invocable Apex (callout=true) + Named Credential. (Pattern similar to Q64, but returns KYC status + reason.)
Response contract
public class KycResult { @InvocableVariable public Boolean ok; @InvocableVariable public String reason; }
Flow
- Action: Validate KYC → Decision: if
ok = false
show Screen to collect docs.
Best practices
- Surface reason to users; log requestId for traceability.
Q73. Scenario: Show/hide Flow screen sections based on previous answers.
Answer: Use Component Visibility and Decision.
Design
- Screen 1: “Are you an enterprise customer?” (Yes/No).
- Screen 2: Fields with Visibility =
{!isEnterprise} = TRUE
. - Decision branches for different paths.
Best practices
- Keep screens short; one intent per step for better completion rates.
Q74. Scenario: Allow only certain profiles to run a sensitive Flow step.
Answer: Gate with Custom Permission.
Design
- Custom Permission:
Can_Run_Discount_Override
- Permission Set grants it to approved roles.
- Flow Decision:
HasCustomPermission('Can_Run_Discount_Override') = TRUE
→ allow branch; else show message.
Best practices
- Custom Permission > Profile checks (portable & auditable).
- Avoid hardcoding Profile names.
Q75. Scenario: Mass delete old records safely without timeouts.
Answer: Use Scheduled Flow with paging (or Batch Apex for very large sets).
Design (Flow)
- Schedule nightly.
- Get Records: e.g., Cases Closed > 365 days (limit to 200 or use All records with Loop).
- Loop → Delete Records in small chunks (use a collection variable and delete per chunk of 100).
Best practices
- Prefer Batch Apex for >10k to avoid limits.
- During heavy maintenance, enable Deferred Sharing Recalculation.
🔹 Section 6: Async Apex & Integrations (Q76–Q100)
Q76. Scenario: Nightly processing of ~5M records without timeouts.
Answer: Use Batch Apex with a QueryLocator (streams server-side) and small scope (e.g., 200).
Code
global class RecomputeScoresBatch implements Database.Batchable<SObject>, Database.Stateful {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator('SELECT Id FROM Account WHERE IsActive__c = TRUE');
}
global void execute(Database.BatchableContext bc, List<Account> scope) {
for (Account a : scope) { a.Health_Score__c = compute(a); }
update scope;
}
global void finish(Database.BatchableContext bc) {
// email summary, chain another batch, etc.
}
Decimal compute(Account a) { return 80; }
}
Best practices
- Use Stateful for cross-chunk aggregation (counts, totals).
- Keep per-chunk logic idempotent (safe to re-run).
- Consider Deferred Sharing Calculation during big jobs.
Q77. Scenario: Chain jobs dynamically (e.g., step-2 after step-1 completes).
Answer: Use Queueable Apex and enqueue the next job in finish
or at the end of execute
.
Code
public class Step1 implements Queueable {
public void execute(QueueableContext qc) {
// work...
System.enqueueJob(new Step2());
}
}
public class Step2 implements Queueable { public void execute(QueueableContext qc) { /* work */ } }
Best practices
- From one queueable, you can enqueue one more immediately (chain).
- For longer pipelines, chain step-by-step; log a CorrelationId.
Q78. Scenario: Run a job daily at 2:00 AM IST.
Answer: Implement Schedulable and schedule with a CRON.
Code
global class NightlyJob implements Schedulable {
global void execute(SchedulableContext sc) {
Database.executeBatch(new RecomputeScoresBatch(), 200);
}
}
// One-time setup (e.g., in Anonymous Apex)
String cron = '0 0 2 * * ?'; // 2:00 AM every day (org time zone)
System.schedule('Nightly Recompute', cron, new NightlyJob());
Best practices
- If business-critical, re-schedule on deploys to avoid duplicates.
- Use Custom Metadata to hold schedule windows.
Q79. Scenario: Make external callouts asynchronously and reliably.
Answer: Use Queueable implements Database.AllowsCallouts (more flexible than @future
).
Code
public class PostToWebhook implements Queueable, Database.AllowsCallouts {
String payload;
public PostToWebhook(String json){ this.payload = json; }
public void execute(QueueableContext qc){
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:MY_WEBHOOK'); // Named Credential
req.setMethod('POST'); req.setHeader('Content-Type','application/json');
req.setBody(payload);
new Http().send(req);
}
}
Best practices
- Always use Named Credentials; never hardcode URLs/keys.
- Wrap with retry logic for transient errors (see Q82).
Q80. Scenario: External API can take up to 2 minutes — avoid CPU limits.
Answer: Use Continuation (supported in Visualforce and Aura).
Code (Apex)
public with sharing class LongCallController {
public Continuation cont;
@AuraEnabled
public static Object doCallout() {
Continuation c = new Continuation(120); // timeout seconds
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:LONG_API'); req.setMethod('GET');
String label = c.addHttpRequest(req);
return c; // framework resumes later and returns the response
}
}
Best practices
- Use Continuation for long-running calls in VF/Aura.
- For LWC, prefer Queueable + UI polling or Platform Events to notify completion.
Q81. Scenario: Publish an event when Opportunity becomes Closed Won.
Answer: Use Platform Events and publish from Apex/Flow.
Code (Publish)
Opportunity_Won__e evt = new Opportunity_Won__e(
OpportunityId__c = opp.Id,
Amount__c = opp.Amount,
OwnerEmail__c = opp.Owner.Email
);
Database.SaveResult sr = EventBus.publish(evt);
Best practices
- Use replay (external subscriber) to recover after downtime.
- Keep event payloads small; include an ExternalId/CorrelationId.
Q82. Scenario: Retry failed callouts with exponential backoff.
Answer: Log attempts in a custom object and re-enqueue Queueable/Schedulable.
Pattern
- On failure → increment
Attempts__c
, computeNext_Run_At__c
(e.g., 5m, 15m, 1h). - A scheduled job queries due items and re-enqueues work.
Code (retry calculator)
Integer nextDelayMins(Integer attempts) {
Integer[] backoff = new Integer[]{5, 15, 60, 180}; // cap at 3h
return backoff[Math.min(attempts, backoff.size()-1)];
}
Best practices
- Never
sleep()
in Apex. Use Schedules or Platform Events. - Stop after N attempts; move to a Failed_Integration__c “DLQ” object.
Q83. Scenario: Handle 10k+ outbound emails daily reliably.
Answer: Use Batch Apex + Messaging.SingleEmailMessage
or Email Alerts via Flow.
Code (batch snippet)
Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
m.setToAddresses(new String[]{'user@x.com'});
m.setSubject('Hello'); m.setPlainTextBody('Body');
Messaging.sendEmail(new Messaging.SingleEmailMessage[]{ m }, false);
Best practices
- Respect daily org limits; throttle in batch.
- Prefer Email Alerts with templates when feasible.
Q84. Scenario: Upsert Orders from ERP, avoiding duplicates.
Answer: Mark ExternalId__c
on your object and upsert.
Code
List<Order__c> orders = new List<Order__c>();
orders.add(new Order__c(ExternalId__c='ORD-1001', Amount__c=500));
Database.UpsertResult[] res = Database.upsert(orders, Order__c.ExternalId__c, false);
Best practices
- ExternalId ensures idempotency — same payload won’t create duplicates.
- Store last processed timestamp from ERP to resume safely.
Q85. Scenario: Real-time integration Salesforce → ERP on events.
Answer: Publish Platform Events on business milestones and have ERP subscribe (Pub/Sub API or CometD).
Pattern
- Event:
Opportunity_Won__e
- ERP subscription with ReplayId to catch up after downtime.
Best practices
- Use 1 event type per intent (clear contract).
- Keep payload minimal; ERP fetches details via REST if required.
Q86. Scenario: Bulk load 1M records from Data Lake.
Answer: Use Bulk API v2 (preferred for large loads) or Apex + Batch if transforming in-org.
Tips
- Split files into ~10MB–100MB chunks; use parallel mode when no parent-child constraints.
- For updates, ensure filters are selective (indexed keys).
Q87. Scenario: Secure external calls with OAuth 2.0 (no secrets in code).
Answer: Use Named Credentials + Auth Provider. Call with callout:NAME
.
Code
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ERP_API/orders'); // Named Credential handles OAuth
req.setMethod('GET');
HttpResponse res = new Http().send(req);
Best practices
- For mTLS or signed JWT, upload certificates in Named Credential.
- Rotate creds centrally; no code changes required.
Q88. Scenario: Track partial DML failures in batch and summarize.
Answer: Use Database.insert/update
with allOrNone=false
and capture SaveResults.
Code
Database.SaveResult[] results = Database.update(records, false);
Integer ok=0, fail=0;
for (Integer i=0; i<results.size(); i++) {
if (results[i].isSuccess()) ok++;
else {
fail++;
for (Database.Error e : results[i].getErrors()) {
// log record Id + e.getStatusCode() + e.getMessage()
}
}
}
// email ok/fail counts in finish()
Best practices
- Log failed payloads for replay; don’t swallow errors.
Q89. Scenario: Post to Slack when Case escalates.
Answer: Queueable callout to Slack Incoming Webhook (via Named Credential).
Code
public class SlackNotify implements Queueable, Database.AllowsCallouts {
Case c; public SlackNotify(Case c){ this.c = c; }
public void execute(QueueableContext qc){
HttpRequest r = new HttpRequest();
r.setEndpoint('callout:SLACK_WEBHOOK');
r.setMethod('POST'); r.setHeader('Content-Type','application/json');
r.setBody('{"text":"Escalated Case ' + c.CaseNumber + '"}');
new Http().send(r);
}
}
Best practices
- Use structured blocks in Slack for better UI; rate-limit if needed.
Q90. Scenario: Callouts inside Batch Apex.
Answer: Implement Database.AllowsCallouts
on the batch class.
Code
global class SyncBatch implements Database.Batchable<SObject>, Database.AllowsCallouts {
global Database.QueryLocator start(Database.BatchableContext bc){ /* ... */ }
global void execute(Database.BatchableContext bc, List<Account> scope){
// safe to call out per chunk
}
global void finish(Database.BatchableContext bc){ }
}
Best practices
- Keep chunks small; external APIs may throttle.
- Consider retry patterns (Q82).
Q91. Scenario: Avoid running the same Queueable twice for the same key.
Answer: Implement idempotency with a locking row or Processed_Job__c keyed by external id.
Pattern
if (!JobLock.tryAcquire('INVOICE:123')) return; // already running
System.enqueueJob(new ProcessInvoiceQueueable('123'));
Best practices
- Release lock in
finish
or after success/failure. - Use Platform Cache or a custom object to implement locks.
Q92. Scenario: Prevent duplicate processing of webhook events.
Answer: Store Event Id (or payload hash) in a Processed_Event__c table; skip if exists.
Code
Boolean seen = [SELECT COUNT() FROM Processed_Event__c WHERE ExternalEventId__c = :evtId] > 0;
if (!seen) {
// process
insert new Processed_Event__c(ExternalEventId__c = evtId);
}
Best practices
- Make
ExternalEventId__c
unique to enforce at DB level.
Q93. Scenario: mTLS (mutual TLS) or signed JWT to partner API.
Answer: Configure Named Credential with a Client Certificate or JWT auth provider.
Tips
- Upload client cert under Certificate and Key Management.
- Reference via Named Credential; code remains unchanged.
Q94. Scenario: Handle UNABLE_TO_LOCK_ROW
gracefully in async processing.
Answer: Catch and retry later.
Code
try {
update records;
} catch (DmlException e) {
if (e.getMessage().contains('UNABLE_TO_LOCK_ROW')) {
// schedule/queue for retry
} else { throw e; }
}
Best practices
- Reduce contention: keyset pagination, FOR UPDATE, or reschedule during off-peak.
Q95. Scenario: Subscribe to Platform Events in LWC (real-time UI).
Answer: Use lightning/empApi
.
LWC JS
import { LightningElement } from 'lwc';
import { subscribe, onError, setDebugFlag } from 'lightning/empApi';
export default class PeSubscriber extends LightningElement {
channelName = '/event/Opportunity_Won__e';
subscription;
connectedCallback() {
setDebugFlag(true);
subscribe(this.channelName, -1, (msg) => this.handle(msg))
.then(res => this.subscription = res);
onError(err => { /* log */ });
}
handle(message) {
// update UI with message.data.payload
}
}
Best practices
- Use replay id -1 for new events; store last ReplayId if you need recovery.
Q96. Scenario: Subscribe to Change Data Capture (CDC) for Account changes.
Answer: Subscribe to /data/AccountChangeEvent
(LWC or external consumer).
LWC (empApi channel)
const channel = '/data/AccountChangeEvent';
subscribe(channel, -1, (msg) => {
const change = msg.data.payload.ChangeEventHeader.changeType; // CREATE/UPDATE/DELETE
// react to change
});
Best practices
- CDC is great for near real-time sync; keep handlers lightweight.
Q97. Scenario: External microservice consumes events reliably at scale.
Answer: Use Pub/Sub API (gRPC/HTTP) with replay and durable subscriptions.
Tips
- Store last acked ReplayId in microservice storage (e.g., Redis/DB).
- Horizontal-scale consumers; partition by entity id to avoid ordering issues.
Q98. Scenario: Sync Salesforce Files to SharePoint/Drive.
Answer: Use ContentVersion + callout to external API; link with ContentDocumentLink.
Code (create file)
ContentVersion v = new ContentVersion(
Title='Invoice', PathOnClient='invoice.pdf', VersionData=blobBody
);
insert v;
ContentDocumentLink l = new ContentDocumentLink(
ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :v.Id].ContentDocumentId,
LinkedEntityId = recordId, ShareType='V'
);
insert l;
// Callout to external to mirror (send presigned URL or binary)
Best practices
- Watch file size limits; use multipart uploads externally.
Q99. Scenario: Monitor health of Batch/Queueable and API callouts.
Answer: Build an Ops Dashboard:
- Query
AsyncApexJob
,ApexTestQueueItem
,PlatformEventUsageMetric
. - Log callout status codes to a custom object; chart failures over time.
Code (sample)
List<AsyncApexJob> jobs = [SELECT Id, JobType, Status, NumberOfErrors, TotalJobItems, CreatedDate
FROM AsyncApexJob ORDER BY CreatedDate DESC LIMIT 50];
Best practices
- Alert on NumberOfErrors > 0 or long-running jobs.
- Keep centralized logs for audits.
Q100. Scenario: Large, continuous data sync between Salesforce and downstream systems.
Answer: Use Change Data Capture (CDC) for near real-time streaming of inserts/updates/deletes; consumers process deltas and upsert to targets.
Pattern
- Enable CDC on key objects (e.g., Account, Contact).
- Consumers subscribe via Pub/Sub API or CometD; store ReplayId.
- Use External Id on the target for idempotent upserts.
Best practices
- Throttle downstream to handle bursts; use backpressure queues.
- Include origin flags to avoid update loops (e.g.,
Source__c = 'ERP'
).
📚 More Resources for You
Here are some resources to go deeper:
🔗 100 Scenarios (1–4 YOE)
🔗 100 Scenarios (4–8 YOE)
🔗 LWC Q&A (Real Answers Explained)
🔗 600+ Qs from Recruiter Calls
🔗 TCS, Infosys, EY Interview Qs
🔗 Salesforce Project – Hindi + English