Site icon Trailhead Titans

🚀 Top 100 Scenario-Based Salesforce Developer Interview Questions (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

Toggle

🔹 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:

📌 Example:


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:

AND(
   ISCHANGED(Status__c), 
   NOT(Editable__c)
)

📌 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:

📌 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:

📌 Why Flow instead of Trigger?


Q5. Scenario: You need to mass-update 100,000 records. Which tool will you use?
👉 Answer:

📌 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:

📌 Why not Process Builder?


Q7. Scenario: Business wants to auto-assign Accounts to different queues based on Region.
👉 Answer:

📌 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:

📌 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:

📌 Example:


Q10. Scenario: Two users need different page layouts for the same object.
👉 Answer:

📌 Example:

🔹 Section 2: Apex & Trigger Scenarios (Q11–Q30)


Q11. Scenario: Insert Account & User in same transaction → Mix DML error. How do you fix?
👉 Answer:

📌 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:

📌 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:

📌 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:

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:

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:

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:

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:

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:


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:


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:

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


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


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


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


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


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

Example

SELECT Id, Name
FROM Account
WHERE IsActive__c = TRUE
AND Name LIKE :namePrefix  // 'Acme%'
AND LastModifiedDate = LAST_N_DAYS:30

Best practices


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


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


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


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


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


Q49. Scenario: Record data skew (1 owner with millions of children) causing locks/timeouts.

Answer: Mitigate ownership & lookup skew.

What to do

Detection


Q50. Scenario: “Non-selective query against large object” error in triggers.

Answer: Make the query selective or move to async.

Fixes

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" >

Best practices


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


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


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


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" >

Best practices


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


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


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


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


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

🔹 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


Q62. Scenario: Create a Task automatically when Opportunity becomes Closed Won.

Answer: After-Save Flow on Opportunity.

Design

Best practices


Q63. Scenario: Collect additional info from user before creating Case.

Answer: Screen Flow launched from a button/quick action.

Design

Best practices


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

Best practices


Q65. Scenario: Mass update related child records (no Apex).

Answer: After-Save Flow with Collection Update.

Design

Best practices


Q66. Scenario: Auto-assign Case to a queue based on Priority.

Answer: Record-Triggered Flow on Case (Before-Save).

Design

Best practices


Q67. Scenario: Nightly mass update (e.g., recalculate Health Score).

Answer: Scheduled-Triggered Flow (or Scheduled Path on an object if event-driven).

Design

Best practices


Q68. Scenario: Reuse a common sub-process in multiple flows.

Answer: Build a Subflow and call it from parent flows.

Design

Best practices


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

Best practices


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

Best practices


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

Best practices


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

Best practices


Q73. Scenario: Show/hide Flow screen sections based on previous answers.

Answer: Use Component Visibility and Decision.

Design

Best practices


Q74. Scenario: Allow only certain profiles to run a sensitive Flow step.

Answer: Gate with Custom Permission.

Design

Best practices


Q75. Scenario: Mass delete old records safely without timeouts.

Answer: Use Scheduled Flow with paging (or Batch Apex for very large sets).

Design (Flow)

Best practices

🔹 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


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


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


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


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


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


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


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


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


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

Best practices


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


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


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


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


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


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


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


Q93. Scenario: mTLS (mutual TLS) or signed JWT to partner API.

Answer: Configure Named Credential with a Client Certificate or JWT auth provider.

Tips


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


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


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


Q97. Scenario: External microservice consumes events reliably at scale.

Answer: Use Pub/Sub API (gRPC/HTTP) with replay and durable subscriptions.

Tips


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


Q99. Scenario: Monitor health of Batch/Queueable and API callouts.

Answer: Build an Ops Dashboard:

Code (sample)

List<AsyncApexJob> jobs = [SELECT Id, JobType, Status, NumberOfErrors, TotalJobItems, CreatedDate
                            FROM AsyncApexJob ORDER BY CreatedDate DESC LIMIT 50];

Best practices


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

Best practices

📚 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