🚀 Top 100 Scenario-Based Salesforce Developer Interview Questions (2025)

Published On: August 27, 2025

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.

Table of Contents

🔹 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):
    1. Entry criteria: Amount > 1000000.
    2. Action: Send Email Alert using a pre-configured Email Template.

📌 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:
    1. When Account field (e.g., Status) changes,
    2. Query related Cases,
    3. 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 with Trigger.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 use refreshApex after updateRecord.

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 trigger refreshApex.

🔹 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

  1. Trigger: Account → When record is updated.
  2. Entry criteria: ISCHANGED({!$Record.Phone}) and NOT(ISBLANK({!$Record.Phone})).
  3. Get Records: Contacts where AccountId = {!$Record.Id}.
  4. 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 and attempts < 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 RecordsUse 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
  • 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 AccountSend 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 and ConvertedContactId = 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

  1. On failure → increment Attempts__c, compute Next_Run_At__c (e.g., 5m, 15m, 1h).
  2. 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

TrailheadTitans

At TrailheadTitans.com, we are dedicated to paving the way for both freshers and experienced professionals in the dynamic world of Salesforce. Founded by Abhishek Kumar Singh, a seasoned professional with a rich background in various IT companies, our platform aims to be the go-to destination for job seekers seeking the latest opportunities and valuable resources.

Related Post

Interview Q & A

🚀 Top 100 Scenario-Based Salesforce Developer Interview Questions (2025)

By TrailheadTitans
|
August 27, 2025
Salesforce Jobs, Interview Q & A

🚀 Top 50 Salesforce Developer Interview Questions (2025 Updated)

By TrailheadTitans
|
August 26, 2025
Blog, Interview Q & A

🚀 15 Real Salesforce Developer Interview Scenarios with Answers & Code (2025)

By TrailheadTitans
|
August 25, 2025

Leave a Comment