🏠 Home 🔒 Record Sharing ⚙ Apex Triggers 🔍 SOQL 💻 LWC 🔗 Integration 🤖 Flows & Automation 🤖 Agentforce & AI 🎈 Agentforce Course — Free ☁ Data Cloud 🎓 DC Course — Free 🚀 DevOps Course — Free 💵 CPQ 🎯 100 Scenario Questions 🏆 150 Advanced Questions 📧 Marketing Cloud 🏗️ Company Wise 👥 About Us Start Learning Free →

Salesforce LWC Interview Questions Part 2 2026 — Shadow DOM, Datatable, Platform Events & Jest Testing

📅  LWC
Salesforce LWC Interview Questions Part 2 (Q41-Q80) | SF Interview Pro
⚡ LWC Part 2

Salesforce LWC Interview Questions Part 2 (Q41–Q80)

Slots, Shadow DOM, Dynamic Components, Datatable, Platform Events, Jest Testing, Performance Optimization & Real Scenarios

Basic Intermediate Advanced 40 Questions · Part 2
Q
Question 41 · 🟠 Intermediate
What are Slots in LWC? Explain Default Slot and Named Slot.
✅ Answer
Slots allow a parent component to inject HTML content into a child component template. Default slot accepts any content; named slots accept specific targeted content via the slot attribute! 🎰
📌 Default Slot
// Child HTML <template> <div class="card"> <slot></slot> <!-- Parent injects content here --> </div> </template> // Parent HTML <c-card-component> <p>This content renders inside child's slot! ✅</p> </c-card-component>
📌 Named Slot
// Child HTML — named slots <template> <div class="modal"> <header><slot name="header"></slot></header> <main><slot></slot></main> <!-- default --> <footer><slot name="footer"></slot></footer> </div> </template> // Parent HTML — targets named slots <c-modal> <span slot="header">Edit Quote</span> <p>Main form content here</p> <!-- goes to default --> <button slot="footer">Save</button> </c-modal>
🔑 Key Points for Interviewer
  • 🔥Slots enable composition pattern — child defines structure, parent provides content
  • 💡Slot content lives in parent's shadow DOM — child CSS cannot style slotted content directly
  • 💡Slot fallback: <slot>Default text if parent provides nothing</slot>
  • ⚠️Named slot: parent must use slot="name" attribute to target the right slot
🎤 One-Line Answer for Interview
"Default slot injects any content into child template; named slots target specific regions via slot attribute. Slot content belongs to parent's shadow DOM — child CSS cannot style it. This is the composition pattern for reusable layout components like cards and modals."
Q
Question 42 · 🔴 Advanced
What is Shadow DOM in LWC? How does it affect CSS and DOM queries?
✅ Answer
Shadow DOM encapsulates each component's DOM and CSS — styles never leak in or out, document.getElementById() always returns null, and parent CSS cannot style child component internals. 🛡️
BehaviorWith Shadow DOM (LWC)Without Shadow DOM
CSS scopeStyles scoped to component onlyStyles leak globally
document.getElementById()Returns null always ❌Works globally
this.template.querySelector()✅ Works within componentN/A
Parent CSS styles child?❌ Blocked by shadow boundaryYes — leaks through
Event crosses boundary?Only with composed:trueYes by default
📌 Crossing Shadow Boundaries
/* ❌ This will NOT style child component elements */ c-child-component p { color: red; } /* ✅ CSS Custom Properties DO pierce Shadow DOM */ c-child-component { --text-color: red; } /* Child uses it: */ p { color: var(--text-color); } /* ✅ :host — child styles its own outer element */ :host { display: block; border-radius: 12px; } :host([variant="large"]) { font-size: 18px; }
🔑 Key Points for Interviewer
  • 🔥CSS Custom Properties are the ONLY way to pierce Shadow DOM for styling
  • 💡:host selector styles the component's own outer element from within
  • 💡Use this.template.querySelector() inside LWC — never document.getElementById()
  • 💡composed:true on CustomEvent is needed for events to cross shadow boundaries
🎤 One-Line Answer for Interview
"Shadow DOM encapsulates each component's DOM and CSS — document queries return null, styles never leak in or out. CSS Custom Properties are the only standard way to pass styling across shadow boundaries. Use this.template.querySelector() for DOM access."
Q
Question 43 · 🟠 Intermediate
What is the difference between lwc:if and if:true in LWC?
✅ Answer
lwc:if is the modern replacement — introduced Spring '23. Supports lwc:elseif and lwc:else chains. Salesforce recommends migrating all components from if:true to lwc:if! ✅
Featurelwc:if (Modern)if:true / if:false (Legacy)
IntroducedSpring '23LWC launch
elseif support✅ lwc:elseif❌ Not available
else support✅ lwc:else❌ Not available
Negationlwc:if={!flag}if:false={flag}
Recommended?✅ Always use⚠️ Legacy — migrate
<!-- ❌ OLD way --> <template if:true={isAdmin}><p>Admin View</p></template> <template if:false={isAdmin}><p>User View</p></template> <!-- ✅ NEW way --> <template lwc:if={isAdmin}> <p>Admin View</p> </template> <template lwc:elseif={isManager}> <p>Manager View</p> </template> <template lwc:else> <p>User View</p> </template>
🔑 Key Points for Interviewer
  • 🔥lwc:if, lwc:elseif, lwc:else must be on <template> tags — not on HTML elements directly
  • 💡lwc:elseif and lwc:else must immediately follow lwc:if or lwc:elseif
  • ⚠️Cannot mix old (if:true) and new (lwc:if) directives in the same template chain
🎤 One-Line Answer for Interview
"lwc:if is the modern directive supporting lwc:elseif and lwc:else chains — introduced Spring '23 to replace the limited if:true/if:false which had no elseif support. Always use lwc:if in new components and migrate legacy components."
Q
Question 44 · 🟠 Intermediate
What is the difference between for:each and iterator in LWC?
✅ Answer
Both render lists — but iterator gives you index, first, and last flags to handle list boundaries. for:each is simpler for standard lists without special first/last logic! 🔁
Featurefor:eachiterator
Syntaxfor:each={items} for:item="item"iterator:it={items}
Access index?❌ No✅ it.index
Access first?❌ No✅ it.first
Access last?❌ No✅ it.last
Key required?✅ key={item.id}✅ key={it.value.id}
Best forStandard listsLists needing first/last logic
<!-- for:each — simple --> <template for:each={lineItems} for:item="item"> <div key={item.Id}>{item.Name}</div> </template> <!-- iterator — first/last logic --> <template iterator:it={lineItems}> <div key={it.value.Id}> <template lwc:if={it.first}><hr/></template> {it.value.Name} — #{it.index} <template lwc:if={it.last}> <p>Total: {lineItems.length} items</p> </template> </div> </template>
🔑 Key Points for Interviewer
  • 🔥key is mandatory on both — must be unique stable ID, never use loop index as key
  • 💡iterator provides: it.value (item), it.index (position), it.first (boolean), it.last (boolean)
  • 💡key must be a string or number — Salesforce record Id is ideal
🎤 One-Line Answer for Interview
"for:each renders lists simply with item access; iterator additionally provides index, first, and last flags for boundary logic. Both require a unique key attribute — always use stable record IDs, never loop index."
Q
Question 45 · 🔴 Advanced
What are Dynamic Components in LWC? How do you use lwc:component?
✅ Answer
Dynamic Components render different LWC components at runtime based on a variable — without knowing the component name at compile time. Uses lwc:component with lwc:is directive! ⚡
// HTML — lwc:component with lwc:is <template> <lwc:component lwc:is={dynamicCmp} record-id={recordId}> </lwc:component> </template> // JS — load constructor dynamically import { LightningElement } from 'lwc'; import { load } from '@lwc/loader'; export default class DynamicContainer extends LightningElement { dynamicCmp; async connectedCallback() { if (this.type === 'Opportunity') { this.dynamicCmp = await load('c/opportunityCard'); } else if (this.type === 'Account') { this.dynamicCmp = await load('c/accountCard'); } } }
FeatureStatic ComponentDynamic Component
Component nameKnown at compile timeDetermined at runtime
Syntax<c-my-component><lwc:component lwc:is={ctor}>
Use caseFixed layoutsConfigurable dashboards, plugin patterns
PerformanceFaster (no async load)Slight delay (async import)
🔑 Key Points for Interviewer
  • 🔥lwc:is takes a component constructor — not a string name
  • 💡Dynamic components enable plugin/configurable architecture patterns
  • 💡The loaded component must exist in the project — cannot load arbitrary external components
  • ⚠️lwc:component requires newer API version — verify org support
🎤 One-Line Answer for Interview
"Dynamic Components use lwc:component with lwc:is to render different LWC components at runtime — the component constructor is loaded asynchronously and assigned to a property. Enables configurable dashboard and plugin architecture patterns."
Q
Question 46 · 🟢 Basic
What are the three lightning-record-form components in LWC? When do you use each?
✅ Answer
Three components — lightning-record-form (simplest), lightning-record-view-form (read-only), lightning-record-edit-form (full control). Choose based on how much customization you need! 📋
ComponentModeControl LevelBest For
lightning-record-formView + Edit + NewLow — minimal codeQuick simple forms, no custom logic
lightning-record-view-formView onlyMedium — custom layoutCustom read-only displays
lightning-record-edit-formEdit + NewHigh — full controlCustom validation, conditional fields
<!-- 1. lightning-record-form — simplest --> <lightning-record-form record-id={recordId} object-api-name="Account" fields={fields}> </lightning-record-form> <!-- 2. lightning-record-view-form --> <lightning-record-view-form record-id={recordId} object-api-name="Account"> <lightning-output-field field-name="Name"></lightning-output-field> <lightning-output-field field-name="Phone"></lightning-output-field> </lightning-record-view-form> <!-- 3. lightning-record-edit-form — full control --> <lightning-record-edit-form record-id={recordId} object-api-name="Account" onsuccess={handleSuccess}> <lightning-messages></lightning-messages> <lightning-input-field field-name="Name"></lightning-input-field> <lightning-button type="submit" label="Save"></lightning-button> </lightning-record-edit-form>
🔑 Key Points for Interviewer
  • 🔥All three automatically respect FLS and sharing rules
  • 💡lightning-record-edit-form: onsuccess fires after successful save — handle toast or navigation
  • 💡lightning-messages inside edit-form shows validation errors automatically
  • 💡lightning-record-form cannot be customized — use edit-form for conditional fields or custom validation
🎤 One-Line Answer for Interview
"lightning-record-form for minimal-code simple forms; lightning-record-view-form for custom read-only layouts; lightning-record-edit-form for full control with custom validation and conditional fields. All three automatically enforce FLS and sharing rules."
Q
Question 47 · 🟠 Intermediate
How do you implement custom field-level validation in lightning-record-edit-form?
✅ Answer
Intercept onsubmit with event.preventDefault(), validate, call setCustomValidity() + reportValidity(), then manually submit if valid! ✅
// HTML <lightning-record-edit-form object-api-name="Account" onsubmit={handleSubmit}> <lightning-input-field field-name="AnnualRevenue" class="revenue-field"> </lightning-input-field> <lightning-button type="submit" label="Save"></lightning-button> </lightning-record-edit-form> // JS handleSubmit(event) { event.preventDefault(); // ← stop default submission const revenueField = this.template.querySelector('.revenue-field'); const revenueValue = event.detail.fields.AnnualRevenue; if (revenueValue < 1000) { revenueField.setCustomValidity('Revenue must be at least ₹1,000'); revenueField.reportValidity(); return; } revenueField.setCustomValidity(''); // clear error this.template.querySelector('lightning-record-edit-form') .submit(event.detail.fields); // manually submit }
🔑 Key Points for Interviewer
  • 🔥Always call event.preventDefault() in onsubmit to intercept before validation
  • 💡setCustomValidity('') clears a previous custom error — always clear before re-validating
  • 💡reportValidity() shows the error in UI — must be called after setCustomValidity()
  • 💡event.detail.fields contains all form field values as an object
🎤 One-Line Answer for Interview
"Intercept onsubmit with event.preventDefault(), validate field values, call setCustomValidity() with an error message and reportValidity() to show it. Clear with setCustomValidity('') and manually call form.submit() if all validations pass."
Q
Question 48 · 🟠 Intermediate
How does lightning-datatable work in LWC? What are its key features?
✅ Answer
lightning-datatable renders tabular data with built-in sorting, inline editing, row selection, and row actions — defined via column configuration, zero HTML table markup needed! 📊
// HTML <lightning-datatable key-field="Id" data={tableData} columns={columns} sorted-by={sortedBy} sorted-direction={sortedDirection} onsort={handleSort} onrowaction={handleRowAction} draft-values={draftValues} onsave={handleSave}> </lightning-datatable> // JS — column definitions columns = [ { label: 'Name', fieldName: 'Name', type: 'text', sortable: true }, { label: 'Amount', fieldName: 'Amount', type: 'currency', typeAttributes: { currencyCode: 'INR' } }, { label: 'Stage', fieldName: 'StageName', type: 'picklist', editable: true }, { type: 'action', typeAttributes: { rowActions: [ { label: 'View', name: 'view' }, { label: 'Delete', name: 'delete' } ]}} ];
FeatureAttribute/Event
Sortingsorted-by, sorted-direction, onsort
Inline editingeditable:true on column, draft-values, onsave
Row selectionselected-rows, onrowselection, max-row-selection
Row actionstype:action on column, onrowaction
Infinite scrollenable-infinite-loading, onloadmore
🔑 Key Points for Interviewer
  • 🔥key-field is mandatory — must be unique field per row, always use Id
  • 💡Inline editing: draft-values holds unsaved edits — commit with onsave event
  • 💡Sorting: framework sorts UI only — for server-side sorting, re-fetch in onsort handler
  • 💡Custom row actions: event.detail.action.name identifies which action was clicked
🎤 One-Line Answer for Interview
"lightning-datatable renders tabular data with built-in sorting, inline editing, row selection, and row actions via column definitions. key-field is mandatory. Inline edits accumulate in draft-values until onsave fires."
Q
Question 49 · 🔴 Advanced
How do you implement inline editing in lightning-datatable with Apex save?
✅ Answer
Set editable:true on columns — edits accumulate in draft-values. Handle onsave event to call Apex, then clear draft-values on success and refreshApex! ✏️
columns = [ { label: 'Name', fieldName: 'Name', type: 'text', editable: true }, { label: 'Amount', fieldName: 'Amount', type: 'currency', editable: true } ]; @track draftValues = []; async handleSave(event) { const updatedFields = event.detail.draftValues; const recordsToUpdate = updatedFields.map(draft => ({ Id: draft.Id, ...draft })); try { await updateAccounts({ accounts: recordsToUpdate }); this.dispatchEvent(new ShowToastEvent({ title: 'Success', message: 'Records updated!', variant: 'success' })); this.draftValues = []; // ← MUST clear after save await refreshApex(this.wiredAccounts); } catch (error) { this.dispatchEvent(new ShowToastEvent({ title: 'Error', message: error.body.message, variant: 'error' })); } }
🔑 Key Points for Interviewer
  • 🔥Clear draftValues = [] after successful save — otherwise edits linger in the table
  • 💡event.detail.draftValues = array of objects with Id + only changed fields
  • 💡refreshApex() after save keeps wire data in sync with server
  • 💡On error: keep draftValues intact so user can retry without re-entering data
🎤 One-Line Answer for Interview
"Inline editing accumulates changes in draft-values. Handle onsave with event.detail.draftValues, call Apex with changed records, then clear draftValues = [] on success and refreshApex() to sync wire data."
Q
Question 50 · 🔴 Advanced
How do you subscribe to Platform Events in LWC using EmpApi?
✅ Answer
Use subscribe() from lightning/empApi in connectedCallback — always unsubscribe in disconnectedCallback. Platform Events enable real-time updates without page refresh! 📡
import { subscribe, unsubscribe, onError, isEmpEnabled } from 'lightning/empApi'; export default class RealtimeNotifications extends LightningElement { subscription = null; channelName = '/event/OrderUpdate__e'; connectedCallback() { isEmpEnabled().then(enabled => { if (enabled) this.subscribeToChannel(); }); onError(error => console.error('EmpApi error:', error)); } subscribeToChannel() { const replayId = -1; // -1 = new events only, -2 = all retained subscribe(this.channelName, replayId, (message) => { const payload = message.data.payload; this.handleUpdate(payload); }).then(response => { this.subscription = response; // save for unsubscribe }); } disconnectedCallback() { if (this.subscription) { unsubscribe(this.subscription, r => console.log('Unsubscribed:', r)); } } }
🔑 Key Points for Interviewer
  • 🔥Always unsubscribe in disconnectedCallback — subscriptions persist after component removal
  • 💡replayId -1 = new events only (recommended); -2 = all retained events (last 24 hours)
  • 💡Platform Events: max 2,000 messages retained for 24 hours
  • 💡EmpApi uses CometD (Bayeux protocol) streaming under the hood
🎤 One-Line Answer for Interview
"Subscribe to Platform Events using subscribe() from lightning/empApi in connectedCallback — save the subscription object and unsubscribe in disconnectedCallback. replayId -1 receives new events only. EmpApi uses CometD streaming for real-time push."
Q
Question 51 · 🟢 Basic
How do you use Static Resources in LWC?
✅ Answer
Import with @salesforce/resourceUrl/ResourceName and load JS/CSS libraries with loadScript/loadStyle from platformResourceLoader in renderedCallback! 📦
// Image resource import LOGO_URL from '@salesforce/resourceUrl/companyLogo'; logoUrl = LOGO_URL; // use in template: <img src={logoUrl} /> // JS/CSS library (Chart.js, Leaflet.js etc.) import { loadScript, loadStyle } from 'lightning/platformResourceLoader'; import CHARTJS from '@salesforce/resourceUrl/ChartJS'; chartJsInitialized = false; renderedCallback() { if (this.chartJsInitialized) return; // ← prevent re-loading this.chartJsInitialized = true; Promise.all([ loadScript(this, CHARTJS) ]) .then(() => { this.initializeChart(); // library now available }) .catch(error => console.error('Load error:', error)); }
🔑 Key Points for Interviewer
  • 🔥loadScript/loadStyle in renderedCallback — not connectedCallback (DOM needed)
  • 💡Always use a boolean guard to prevent re-loading on every re-render
  • 💡Promise.all() loads multiple resources in parallel — faster than sequential
  • 💡Zip static resource: access sub-files with RESOURCE + '/folder/file.js'
🎤 One-Line Answer for Interview
"Import static resource URLs with @salesforce/resourceUrl/ResourceName. For JS/CSS libraries use loadScript/loadStyle from platformResourceLoader in renderedCallback — always with a boolean guard to prevent re-loading on every re-render."
Q
Question 52 · 🟢 Basic
How do you use Custom Labels in LWC?
✅ Answer
Import with @salesforce/label/c.LabelName — compile-time constants with full Translation Workbench i18n support. Zero runtime API calls! 🌐
import SAVE_LABEL from '@salesforce/label/c.Save_Button'; import ERROR_MSG from '@salesforce/label/c.Generic_Error_Message'; import GREETING from '@salesforce/label/c.Welcome_Message'; export default class MyComponent extends LightningElement { // Group labels in a label object for clean template access label = { save: SAVE_LABEL, error: ERROR_MSG }; // For labels with merge fields {0}: get greetingMessage() { return GREETING.replace('{0}', this.userName); } } // HTML usage: // <lightning-button label={label.save}></lightning-button> // <p class="error">{label.error}</p>
🔑 Key Points for Interviewer
  • 🔥Custom Labels respect Salesforce Translation Workbench — automatically shows translated text
  • 💡Convention: group labels in a label = {} object property for clean template references
  • 💡Labels are compile-time injected — no runtime API call, instant rendering
  • 💡Namespace: c.LabelName for org labels; use namespace prefix for managed package labels
🎤 One-Line Answer for Interview
"Import custom labels with @salesforce/label/c.LabelName — compile-time constants respecting Salesforce Translation Workbench for i18n. Group them in a label = {} object for clean template references like {label.save}."
Q
Question 53 · 🔴 Advanced
How do you implement file upload in LWC?
✅ Answer
Use lightning-file-upload — handles chunked uploads automatically and links files to records via record-id. Handle onuploadfinished for feedback! 📎
<!-- lightning-file-upload --> <lightning-file-upload label="Upload Document" name="fileUploader" accept={acceptedFormats} record-id={recordId} onuploadfinished={handleUploadFinished} multiple> </lightning-file-upload> // JS get acceptedFormats() { return ['.pdf', '.png', '.jpg', '.docx']; } handleUploadFinished(event) { const uploadedFiles = event.detail.files; // Files already linked to recordId automatically! this.dispatchEvent(new ShowToastEvent({ title: 'Success', message: uploadedFiles.length + ' file(s) uploaded', variant: 'success' })); // event.detail.files = [{ name, documentId, contentVersionId }] }
🔑 Key Points for Interviewer
  • 🔥lightning-file-upload handles chunking automatically for large files
  • 💡record-id links uploaded ContentDocument to the record automatically
  • 💡accept attribute filters file types in browser picker — also validate server-side
  • 💡event.detail.files returns array of {name, documentId, contentVersionId, contentBodyId}
🎤 One-Line Answer for Interview
"lightning-file-upload handles chunked uploads automatically and links files to records via record-id. Handle onuploadfinished to show feedback — event.detail.files contains uploaded file metadata. For processing after upload use a ContentVersion Apex trigger."
Q
Question 54 · 🔴 Advanced
How do you build a custom Modal in LWC?
✅ Answer
Build with @api isOpen, a fixed-position backdrop div, and CustomEvent for close. Use CSS position:fixed and z-index for overlay, slots for content injection! 🪟
// modalComponent.html <template> <template lwc:if={isOpen}> <div class="backdrop" onclick={handleBackdropClick}></div> <div class="modal" role="dialog" aria-modal="true"> <div class="modal-header"> <h2>{title}</h2> <lightning-button-icon icon-name="utility:close" onclick={handleClose}></lightning-button-icon> </div> <div class="modal-body"><slot></slot></div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </template> </template> // JS @api isOpen = false; @api title = 'Modal'; handleClose() { this.dispatchEvent(new CustomEvent('close')); } // CSS .backdrop { position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:999; } .modal { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff; border-radius:12px; z-index:1000; min-width:480px; }
🔑 Key Points for Interviewer
  • 🔥Use CSS position:fixed with z-index for overlay — not absolute
  • 💡aria-modal="true" and role="dialog" for accessibility compliance
  • 💡Parent controls isOpen state — modal fires close event, parent decides whether to close
  • 💡Use slots for modal content — makes the modal reusable across different use cases
🎤 One-Line Answer for Interview
"Build custom modal with fixed-position backdrop, centered dialog container, and default/named slots for content. Parent controls isOpen @api property; modal fires a close CustomEvent and parent handles it. Add aria-modal and role attributes for accessibility."
Q
Question 55 · 🔴 Advanced
How do you implement infinite scroll in LWC?
✅ Answer
Use lightning-datatable enable-infinite-loading with onloadmore for tables, or IntersectionObserver for custom lists. Fetch next page in the handler! ∞
// lightning-datatable infinite scroll <lightning-datatable key-field="Id" data={records} columns={columns} enable-infinite-loading onloadmore={handleLoadMore} is-loading={isLoading}> </lightning-datatable> isLoading = false; currentOffset = 0; pageSize = 50; totalCount = 0; records = []; async handleLoadMore() { if (this.records.length >= this.totalCount) return; this.isLoading = true; const result = await getAccountsPage({ pageSize: this.pageSize, offset: this.currentOffset }); this.records = [...this.records, ...result.records]; this.totalCount = result.totalCount; this.currentOffset += this.pageSize; this.isLoading = false; } // IntersectionObserver for custom list renderedCallback() { const sentinel = this.template.querySelector('.sentinel'); if (sentinel && !this.observer) { this.observer = new IntersectionObserver(entries => { if (entries[0].isIntersecting) this.loadNextPage(); }, { threshold: 0.5 }); this.observer.observe(sentinel); } } disconnectedCallback() { if (this.observer) this.observer.disconnect(); }
🔑 Key Points for Interviewer
  • 🔥Always check: if records.length >= totalCount → stop loading
  • 💡lightning-datatable onloadmore fires when user scrolls to bottom — load next batch
  • 💡IntersectionObserver: observe a sentinel element at bottom of list
  • 💡SOQL OFFSET limit: 2,000 — use keyset pagination (WHERE Id > lastId) for larger datasets
🎤 One-Line Answer for Interview
"Use lightning-datatable enable-infinite-loading with onloadmore for table infinite scroll, or IntersectionObserver on a sentinel element for custom lists. Always track total count and stop loading when all records are fetched."
Q
Question 56 · 🟠 Intermediate
What is @salesforce/schema and why is it better than string field names?
✅ Answer
@salesforce/schema provides compile-time field validation — typos in field names are caught at deployment, not runtime. Essential for type-safe LWC development! 🛡️
// ❌ String-based — typo compiles fine, breaks at runtime @wire(getRecord, { recordId: '$recordId', fields: ['Account.Naem'] // typo — NOT caught ❌ }) // ✅ Schema imports — typo caught at DEPLOY time import ACCOUNT_OBJECT from '@salesforce/schema/Account'; import ACCOUNT_NAME from '@salesforce/schema/Account.Name'; import ACCOUNT_PHONE from '@salesforce/schema/Account.Phone'; @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME, ACCOUNT_PHONE] // ← compile-time validated ✅ }) wiredAccount; // Use in createRecord async handleCreate() { const fields = {}; fields[ACCOUNT_NAME.fieldApiName] = this.name; await createRecord({ apiName: ACCOUNT_OBJECT.objectApiName, fields }); }
🔑 Key Points for Interviewer
  • 🔥Schema imports = deploy-time validation — string field names = runtime failure
  • 💡.fieldApiName gives just the field name; full import gives Object.Field reference
  • 💡Works for standard AND custom fields/objects — same syntax
  • 💡Automatic field tracking: Salesforce knows which fields are used — permissions enforced
🎤 One-Line Answer for Interview
"@salesforce/schema imports provide compile-time field validation — misspelled field names fail at deployment immediately instead of silently breaking at runtime. Always use schema imports with getRecord and uiRecordApi operations for type-safe field references."
Q
Question 57 · 🔴 Advanced
How do you optimize LWC performance?
✅ Answer
Memoize getters, debounce inputs, lazy load heavy components, avoid DOM queries in loops, server-side pagination for large data, Promise.all for parallel Apex calls.
Anti-PatternProblemFix
Getter without memoizationRuns on every renderCache with _cached property
loadScript every renderMultiple library loadsBoolean guard in renderedCallback
Loading all recordsBrowser memory/crashServer-side pagination
Multiple sequential ApexSlow — one at a timePromise.all() for parallel
@track on large objectsDeep watching is expensiveTrack only needed properties
// ❌ Bad — runs on EVERY render get filteredItems() { return this.items.filter(i => i.active).sort(...); } // ✅ Memoize — compute only when source changes @track _filteredItems; get filteredItems() { if (!this._filteredItems) { this._filteredItems = this.items.filter(i => i.active).sort(...); } return this._filteredItems; } set items(val) { this._items = val; this._filteredItems = null; } // ✅ Lazy render — only mount when needed @track showChart = false; handleShowChart() { this.showChart = true; } // ✅ Parallel Apex calls const [opps, contacts] = await Promise.all([getOpps(), getContacts()]);
🔑 Key Points for Interviewer
  • 🔥Memoize expensive getters — they run on every render cycle without caching
  • 💡Lazy render heavy components — only mount expensive components when user needs them
  • 💡Wire cache: LDS client cache prevents redundant server calls for same record data
  • 💡Debounce search inputs — prevents Apex calls on every keystroke
🎤 One-Line Answer for Interview
"LWC performance: memoize expensive getters, server-side pagination for large datasets, lazy load heavy components, load static resources once with boolean guards, and Promise.all for parallel Apex calls. Never load all records into browser memory."
Q
Question 58 · 🔴 Advanced
How do you implement LWC Jest testing?
✅ Answer
LWC Jest uses @salesforce/sfdx-lwc-jest — test rendering, wire mock data, user interactions, and Apex responses. Always use element.shadowRoot.querySelector! 🧪
import { createElement } from 'lwc'; import MyComponent from 'c/myComponent'; import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; import getAccounts from '@salesforce/apex/AccountController.getAccounts'; const mockGetAccounts = registerApexTestWireAdapter(getAccounts); describe('MyComponent', () => { let element; beforeEach(() => { element = createElement('c-my-component', { is: MyComponent }); document.body.appendChild(element); }); afterEach(() => { document.body.removeChild(element); jest.clearAllMocks(); }); it('renders accounts from wire', async () => { mockGetAccounts.emit([{ Id: '001', Name: 'Acme Corp' }]); await Promise.resolve(); const items = element.shadowRoot.querySelectorAll('.account-name'); expect(items[0].textContent).toBe('Acme Corp'); }); it('fires custom event on button click', async () => { const handler = jest.fn(); element.addEventListener('select', handler); element.shadowRoot.querySelector('button').click(); await Promise.resolve(); expect(handler).toHaveBeenCalled(); }); });
🔑 Key Points for Interviewer
  • 🔥Use element.shadowRoot.querySelector() — not document.querySelector()
  • 💡registerApexTestWireAdapter: mock wire; jest.fn(): mock imperative Apex
  • 💡await Promise.resolve() after wire.emit() to wait for DOM re-render
  • 💡Always clean up in afterEach: remove element and jest.clearAllMocks()
🎤 One-Line Answer for Interview
"LWC Jest tests use createElement to instantiate components, registerApexTestWireAdapter to mock wire data, and element.shadowRoot.querySelector for DOM assertions. Always await Promise.resolve() after data changes and clean up in afterEach."
Q
Question 59 · 🟠 Intermediate
What are CSS Custom Properties (Design Tokens) in LWC?
✅ Answer
CSS Custom Properties are the ONLY way to pass styling across Shadow DOM boundaries — parent defines the variable, child consumes it with var(). They pierce Shadow DOM! 🎨
/* Parent CSS — defines the token */ c-child-card { --card-bg: #f0f7ff; --card-border: 2px solid #6C63FF; } /* Child CSS — consumes with fallback */ :host { background: var(--card-bg, #ffffff); border: var(--card-border, 1px solid #eee); } /* Salesforce SLDS Design Tokens */ .my-btn { background: var(--lwc-brandColorPrimary); font-size: var(--lwc-fontSize4); border-radius: var(--lwc-borderRadiusMedium); } /* :host conditional styles */ :host([variant="large"]) { font-size: 18px; }
🔑 Key Points for Interviewer
  • 🔥CSS Custom Properties pierce Shadow DOM — the ONLY standard styling bridge
  • 💡var(--property, fallback) — always provide a fallback for robustness
  • 💡Salesforce provides built-in design tokens (--lwc-*) — use for SLDS consistency
  • 💡:host([attribute]) enables conditional styles based on component attributes
🎤 One-Line Answer for Interview
"CSS Custom Properties are the only way to pass styles across Shadow DOM in LWC — parent defines the variable, child consumes with var(). Always use Salesforce design tokens (--lwc-*) for SLDS consistency and provide fallback values."
Q
Question 60 · 🔴 Advanced
How does LWC work in Experience Cloud?
✅ Answer
LWC works by targeting lightningCommunity__Page in metadata. Key limitation: ShowToastEvent does NOT work — build a custom notification component! 🌐
// .js-meta.xml <targets> <target>lightningCommunity__Page</target> <target>lightningCommunity__Default</target> </targets> // Community page navigation navigateToPage() { this[NavigationMixin.Navigate]({ type: 'comm__namedPage', attributes: { name: 'Home' } }); }
FeatureAvailable in Experience Cloud
ShowToastEvent❌ Not available — use custom notification
NavigationMixin✅ Available — use comm__namedPage for community
@wire getRecord✅ Respects guest user sharing
lightning-datatable✅ Available
Apex calls✅ With sharing enforcement
🔑 Key Points for Interviewer
  • 🔥ShowToastEvent does NOT work in Experience Cloud — build a custom notification LWC
  • 💡Guest users: FLS and sharing strictly enforced — test with guest user profile specifically
  • 💡comm__namedPage for community pages, standard__recordPage for record navigation
  • 💡Expose properties via designerAttributes in meta.xml for Experience Cloud Builder
🎤 One-Line Answer for Interview
"LWC in Experience Cloud targets lightningCommunity__Page. ShowToastEvent doesn't work — build a custom notification. Use comm__namedPage for community page navigation. Always test with guest user profile for FLS/sharing enforcement."
Q
Question 61 · 🟠 Intermediate
What is Light DOM in LWC?
✅ Answer
Light DOM (renderMode: light) removes Shadow DOM encapsulation — component HTML becomes part of host document, allowing parent CSS and global frameworks to style it! 💡
// Enable Light DOM export default class LightDomComp extends LightningElement { static renderMode = 'light'; } // Now parent CSS CAN style internals: .my-content { color: red; } // ← works ✅ (blocked with Shadow DOM ❌) // document.querySelector works too: document.querySelector('.my-content'); // returns element ✅
FeatureShadow DOMLight DOM
CSS encapsulation✅ Styles scoped❌ Global CSS applies
Parent CSS styling❌ Blocked✅ Works
Global CSS frameworks❌ Cannot style✅ Can style
Best forIsolated app componentsMarketing/content sites
🔑 Key Points for Interviewer
  • 🔥Light DOM: static renderMode = 'light' in the component class
  • 💡Use for: Experience Cloud branding, third-party CSS framework integration
  • 💡Avoid for: application components where style isolation matters
  • 💡Introduced Summer '23 — verify org API version support
🎤 One-Line Answer for Interview
"Light DOM removes Shadow DOM by setting static renderMode = 'light' — parent CSS and global frameworks can style the component. Use for content/marketing pages; avoid for application components needing style isolation."
Q
Question 62 · 🟠 Intermediate
How do you handle Apex errors in LWC?
✅ Answer
Wire: check the error property in {data, error}. Imperative: use try/catch. Always extract error.body.message — it can be an Array or Object! ❌
// Wire error handling @wire(getAccount, { recordId: '$recordId' }) wiredAccount({ data, error }) { if (data) { this.account = data; this.error = undefined; } if (error) { this.error = this.extractError(error); } } // Imperative async handleSave() { try { await saveAccount({ account: this.account }); } catch (error) { this.showToast('Error', this.extractError(error), 'error'); } } // Universal extractor — handle all error shapes extractError(error) { if (Array.isArray(error.body)) { return error.body.map(e => e.message).join(', '); } else if (error.body?.message) { return error.body.message; // Apex exception } else if (error.message) { return error.message; // JS error } return 'Unknown error'; }
🔑 Key Points for Interviewer
  • 🔥error.body.message = Apex exception. error.message = JS error. Handle both
  • 💡Wire error: set data = undefined when error fires — stale data can confuse UI
  • 💡error.body can be Array (multiple validation errors) or Object (single error)
  • 💡Build reusable extractError() in a service component and import across all LWCs
🎤 One-Line Answer for Interview
"Wire errors come through the error property — always handle both data and error paths. Imperative errors use try/catch. Extract error.body.message for Apex errors and error.message for JS errors. Build a shared extractError utility."
Q
Question 63 · 🟠 Intermediate
How do you access DOM elements in LWC?
✅ Answer
Use this.template.querySelector() or modern lwc:ref + this.refs — always in renderedCallback or event handlers, never in connectedCallback! 🔍
// ✅ In renderedCallback renderedCallback() { const input = this.template.querySelector('[data-id="search"]'); if (input) input.focus(); } // ✅ In event handler handleSubmit() { const nameInput = this.template.querySelector('.name-input'); nameInput.setCustomValidity('Required'); nameInput.reportValidity(); } // ✅ Modern lwc:ref (Summer '23+) // HTML: <input lwc:ref="searchInput" /> handleSearch() { this.refs.searchInput.focus(); } // ❌ NEVER in connectedCallback — DOM not rendered yet connectedCallback() { this.template.querySelector() // null here ❌ }
🔑 Key Points for Interviewer
  • 🔥DOM queries in connectedCallback() return null — DOM not yet rendered
  • 💡renderedCallback fires after EVERY render — use boolean guard for one-time operations
  • 💡Never document.getElementById() — Shadow DOM blocks it
  • 💡lwc:ref is cleaner than querySelector for frequently accessed elements
🎤 One-Line Answer for Interview
"Access DOM using this.template.querySelector() in renderedCallback or event handlers — never in constructor or connectedCallback. Use lwc:ref + this.refs for a cleaner modern approach. document.getElementById() always returns null in LWC."
Q
Question 64 · 🔴 Advanced
How do you build a custom lookup component in LWC?
✅ Answer
Debounced input → Apex SOSL search → dropdown results → user selects → fire CustomEvent. Minimum 2 chars, 300ms debounce, clear on selection! 🔎
export default class CustomLookup extends LightningElement { @api objectApiName = 'Account'; searchTerm = ''; results = []; selectedRecord = null; debounceTimer; handleSearchChange(event) { clearTimeout(this.debounceTimer); const term = event.target.value; if (term.length < 2) { this.results = []; return; } this.debounceTimer = setTimeout( () => this.searchRecords(term), 300 ); } async searchRecords(term) { try { this.results = await searchRecords({ objectApiName: this.objectApiName, searchTerm: '%' + term + '%' }); } catch(e) { this.results = []; } } handleSelect(event) { const id = event.currentTarget.dataset.id; const name = event.currentTarget.dataset.name; this.selectedRecord = { Id: id, Name: name }; this.results = []; this.searchTerm = name; this.dispatchEvent(new CustomEvent('lookupselect', { detail: { record: this.selectedRecord } })); } handleClear() { this.selectedRecord = null; this.searchTerm = ''; this.dispatchEvent(new CustomEvent('lookupselect', { detail: { record: null } })); } }
🔑 Key Points for Interviewer
  • 🔥Always debounce (300ms) and require minimum 2 characters before searching
  • 💡SOSL (FIND ... RETURNING) is better for multi-field text search than SOQL LIKE
  • 💡Close dropdown on select, show selected name in input
  • 💡Fire null detail on clear — parent must handle record removal
🎤 One-Line Answer for Interview
"Custom lookup uses debounced input to call Apex search, displays results in a dropdown, fires a CustomEvent on selection. Key patterns: 2-char minimum, 300ms debounce, SOSL search, close dropdown on select, fire null on clear."
Q
Question 65 · 🟠 Intermediate
How do you expose LWC properties to Salesforce App Builder?
✅ Answer
Use @api with property elements in targetConfig of .js-meta.xml — they appear as configurable in App Builder's property panel! 🎛️
// .js-meta.xml <LightningComponentBundle> <isExposed>true</isExposed> <targets> <target>lightning__RecordPage</target> </targets> <targetConfigs> <targetConfig targets="lightning__RecordPage"> <property name="title" type="String" label="Card Title" default="My Component"/> <property name="maxRecords" type="Integer" label="Max Records" default="5" min="1" max="50"/> <property name="displayMode" type="String" datasource="table,cards,list" default="table"/> <property name="showFooter" type="Boolean" default="true"/> </targetConfig> </targetConfigs> </LightningComponentBundle> // JS — @api receives from App Builder @api title = 'My Component'; @api maxRecords = 5; @api displayMode = 'table'; @api showFooter = true;
🔑 Key Points for Interviewer
  • 🔥Property names in targetConfig MUST match @api property names exactly (case-sensitive)
  • 💡datasource: comma-separated values show as dropdown in App Builder
  • 💡Type options: String, Integer, Boolean, Color, List
  • 💡isExposed must be true for component to appear in App Builder
🎤 One-Line Answer for Interview
"Expose LWC properties to App Builder by adding property elements in targetConfig of .js-meta.xml — names must exactly match @api property names in JS. Supports String, Integer, Boolean, and datasource for dropdowns. isExposed must be true."
Q
Question 66 · 🔴 Advanced
How do you manage complex state in large LWC applications?
✅ Answer
Parent as SSOT for related trees, LMS for unrelated components, JavaScript service module for global state. Never deep @api prop drill! 🏗️
PatternWhen to Use
Parent as SSOTRelated component trees — simplest
LMSUnrelated cross-section components
JS Service ModuleComplex global state across many components
Wire cache (LDS)Shared server data — auto-synced on update
// JS Service Module (global state) // sharedState.js — NO html, NO default class let state = { selectedId: null, filters: {} }; const subscribers = new Map(); export function getState() { return {...state}; } export function setState(updates) { state = {...state, ...updates}; subscribers.forEach(cb => cb(state)); } export function subscribe(key, cb) { subscribers.set(key, cb); } export function unsubscribe(key) { subscribers.delete(key); } // Consumer import { getState, setState, subscribe, unsubscribe } from 'c/sharedState'; connectedCallback() { subscribe(this.uniqueId, state => this.selectedId = state.selectedId); } disconnectedCallback() { unsubscribe(this.uniqueId); }
🔑 Key Points for Interviewer
  • 🔥Default: Parent as SSOT — simplest and most maintainable
  • 💡LMS for cross-section communication between unrelated components
  • 💡JS singleton for complex global state across many deep components
  • 💡Avoid @api prop drilling through 3+ levels — restructure or use LMS
🎤 One-Line Answer for Interview
"Use parent as SSOT for related component trees, LMS for unrelated cross-section components, and a JavaScript singleton service module for complex global state. Avoid deep @api prop drilling — restructure the tree or use LMS when state passes through more than 3 layers."
Q
Question 67 · 🟠 Intermediate
What is getRelatedListRecords wire adapter?
✅ Answer
getRelatedListRecords fetches related list data without Apex — specify parentRecordId, relatedListId, and fields. Zero Apex, respects FLS automatically! 🔗
import { getRelatedListRecords } from 'lightning/uiRelatedListApi'; export default class ContactRelatedList extends LightningElement { @api recordId; @wire(getRelatedListRecords, { parentRecordId: '$recordId', relatedListId: 'Contacts', fields: ['Contact.Name', 'Contact.Email', 'Contact.Phone'], sortBy: ['Contact.Name'], pageSize: 10 }) relatedContacts; get contacts() { return this.relatedContacts?.data?.records || []; } get contactRows() { return this.contacts.map(r => ({ Id: r.fields.Id.value, Name: r.fields.Name.value, Email: r.fields.Email.value })); } }
🔑 Key Points for Interviewer
  • 🔥getRelatedListRecords is zero-Apex — LDS handles fetching and caching
  • 💡relatedListId = API name of the related list (usually plural: Contacts, Opportunities)
  • 💡Fields: use Object.FieldApiName format in strings — not schema imports for this adapter
  • 💡Use getRelatedListInfo first to discover available related lists
🎤 One-Line Answer for Interview
"getRelatedListRecords fetches related list data without Apex — specify parentRecordId, relatedListId, and fields as strings. Respects FLS and sharing automatically and integrates with LDS client cache."
Q
Question 68 · 🟠 Intermediate
What is the difference between bubbles and composed on CustomEvent?
✅ Answer
bubbles:true = travels up DOM. composed:true = crosses Shadow DOM. Need BOTH for deeply nested events to reach a grandparent! 🫧
CombinationBehavior
false, falseDies at dispatching component
true, falseBubbles within same shadow root only
false, trueCrosses shadow boundary but doesn't bubble up
true, trueBubbles AND crosses all shadow boundaries ✅
// Need BOTH for grandparent to catch grandchild event this.dispatchEvent(new CustomEvent('select', { detail: { id: '001' }, bubbles: true, // travels up DOM composed: true // crosses shadow boundaries })); // Grandparent — can now catch it <c-parent onselect={handleSelect}></c-parent>
🔑 Key Points for Interviewer
  • 🔥Both bubbles AND composed must be true for grandparent to catch grandchild event
  • 💡Best practice: handle events at nearest ancestor — avoid long-distance bubbling chains
  • 💡stopPropagation() stops bubbling; composed:true still needed to cross shadow boundaries
  • 💡Re-throwing at each level is cleaner than deep composed bubbling
🎤 One-Line Answer for Interview
"bubbles:true makes event travel up DOM; composed:true makes it cross Shadow DOM. Both are needed for deeply nested events to reach a grandparent. Best practice: handle events at the nearest ancestor."
Q
Question 69 · 🟠 Intermediate
How do you implement CSV export in LWC?
✅ Answer
Build CSV in JS, create a Blob with URL.createObjectURL(), trigger download with hidden anchor — zero Apex needed! 📥
handleExport() { const csv = this.buildCsv(); this.downloadCsv(csv, 'export.csv'); } buildCsv() { const headers = this.columns.map(c => c.label).join(','); const rows = this.records.map(record => this.columns.map(col => { const v = record[col.fieldName] ?? ''; return `"${String(v).replace(/"/g, '""')}"`; }).join(',') ).join('\n'); return headers + '\n' + rows; } downloadCsv(content, fileName) { const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); // ← prevent memory leak! }
🔑 Key Points for Interviewer
  • 🔥URL.revokeObjectURL() after download — prevents memory leaks
  • 💡Wrap values in quotes and escape internal quotes ("→"") to handle commas in data
  • 💡document.createElement('a') is safe in LWC — unlike document.getElementById()
  • 💡50K+ rows: generate server-side with Apex ContentVersion and provide download URL
🎤 One-Line Answer for Interview
"CSV export: build comma-separated string, create Blob, generate URL with URL.createObjectURL(), trigger download with programmatic anchor click. Always call URL.revokeObjectURL() after download to prevent memory leaks."
Q
Question 70 · 🔴 Advanced
How do you implement dependent picklist in LWC?
✅ Answer
Use lightning-record-edit-form + lightning-input-field which handles dependency automatically. For custom UI: filter getPicklistValues by val.validFor! 📋
// Automatic (recommended) <lightning-record-edit-form object-api-name="Lead"> <lightning-input-field field-name="Industry"></lightning-input-field> <lightning-input-field field-name="Rating"></lightning-input-field> </lightning-record-edit-form> // Manual — getPicklistValues + filter get filteredDependentOptions() { if (!this.dependentValues.data || !this.controllerValue) return []; const { controllerValues, values } = this.dependentValues.data; const controllerIndex = controllerValues[this.controllerValue]; return values .filter(val => val.validFor.includes(controllerIndex)) .map(val => ({ label: val.label, value: val.value })); } handleControllerChange(event) { this.controllerValue = event.detail.value; this.dependentValue = ''; // reset dependent }
🔑 Key Points for Interviewer
  • 🔥lightning-record-edit-form + lightning-input-field handles dependency automatically
  • 💡Manual: use val.validFor (array of valid controller indices) to filter
  • 💡controllerValues maps each controller value to its index number
  • 💡Always reset dependent field value when controller changes
🎤 One-Line Answer for Interview
"For record forms use lightning-record-edit-form + lightning-input-field which handles dependent picklists automatically. For custom UI, filter dependent values using val.validFor against the controller field's index from controllerValues."
Q
Question 71 · 🟠 Intermediate
How do you get picklist values without Apex in LWC?
✅ Answer
Use getPicklistValues from lightning/uiObjectInfoApi — wire getObjectInfo first for the recordTypeId, then use it reactively in getPicklistValues! 📋
import { getPicklistValues, getObjectInfo } from 'lightning/uiObjectInfoApi'; import ACCOUNT_OBJ from '@salesforce/schema/Account'; import TYPE_FIELD from '@salesforce/schema/Account.Type'; // Step 1 — get object info for recordTypeId @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJ }) objectInfo; // Step 2 — get picklist values reactively @wire(getPicklistValues, { recordTypeId: '$objectInfo.data.defaultRecordTypeId', fieldApiName: TYPE_FIELD }) typePicklistValues; get typeOptions() { if (!this.typePicklistValues?.data) return []; return this.typePicklistValues.data.values.map(item => ({ label: item.label, value: item.value })); }
🔑 Key Points for Interviewer
  • 🔥getObjectInfo is step 1 — provides defaultRecordTypeId for getPicklistValues
  • 💡$ prefix on objectInfo.data.defaultRecordTypeId makes the wire reactive
  • 💡Respects FLS — only returns values accessible to current user
  • 💡Simpler: lightning-record-edit-form auto-loads picklist values — zero code
🎤 One-Line Answer for Interview
"Use getPicklistValues from lightning/uiObjectInfoApi — first wire getObjectInfo for defaultRecordTypeId, then pass it reactively to getPicklistValues. Map .values to label/value pairs for lightning-combobox. Zero Apex required."
Q
Question 72 · 🔴 Advanced
How do you implement a multi-step wizard in LWC?
✅ Answer
Track currentStep index, render steps with lwc:if, validate each step before next, accumulate data in separate @track objects, merge on submit! 🧙
export default class Wizard extends LightningElement { @track currentStep = 1; totalSteps = 4; @track stepOneData = {}; @track stepTwoData = {}; get isStepOne() { return this.currentStep === 1; } get isLastStep() { return this.currentStep === this.totalSteps; } get progressPct() { return Math.round((this.currentStep / this.totalSteps) * 100); } handleNext() { if (!this.validateCurrentStep()) return; this.currentStep++; } handlePrevious() { if (this.currentStep > 1) this.currentStep--; } validateCurrentStep() { const inputs = this.template .querySelectorAll('lightning-input, lightning-combobox'); return [...inputs].reduce( (valid, inp) => valid && inp.reportValidity(), true ); } async handleSubmit() { if (!this.validateCurrentStep()) return; const payload = { ...this.stepOneData, ...this.stepTwoData }; await submitWizard({ data: payload }); this.dispatchEvent(new CustomEvent('complete')); } }
🔑 Key Points for Interviewer
  • 🔥Validate EACH step before proceeding — not just the final step
  • 💡Use lightning-progress-indicator for visual step indicator
  • 💡Store step data in separate @track objects — merge on final submit
  • 💡Preserve data when user goes back — don't reset on prev click
🎤 One-Line Answer for Interview
"Multi-step wizard uses currentStep counter with lwc:if, validates with reportValidity() before next, accumulates data in separate @track objects merged on submit. Use lightning-progress-indicator for visual tracking."
Q
Question 73 · 🔴 Advanced
How do you debug LWC components?
✅ Answer
Enable Debug Mode in Setup, Chrome DevTools breakpoints, prefixed console.log, Salesforce Inspector extension, and errorCallback for child errors. 🐛
// Enable Debug Mode: Setup → Debug Mode → Enable for user // (shows readable unminified component JS) // Prefixed console.log connectedCallback() { console.log('[MyComp] connectedCallback, recordId:', this.recordId); } @wire(getRecord, { recordId: '$recordId', fields: FIELDS }) wiredAccount({ data, error }) { // JSON.stringify needed for LWC Proxy objects console.log('[MyComp] wire:', JSON.stringify({ data, error })); } // errorCallback — catch child errors errorCallback(error, stack) { console.error('[MyComp] Child error:', error.message); this.error = error.message; }
ToolBest For
Chrome DevTools + Debug ModeBreakpoints in component JS
console.log with [prefix]Quick state inspection at lifecycle points
Salesforce InspectorViewing wire cache and org data
errorCallbackCatching child component errors
Jest unit testsAutomated regression testing
🔑 Key Points for Interviewer
  • 🔥Enable Debug Mode first — minified code is unreadable in DevTools
  • 💡Prefix console.log with [ComponentName] for easy filtering in console
  • 💡JSON.stringify complex objects — Chrome DevTools shows live proxy objects (confusing)
  • 💡LWC Chrome extension: inspect component properties and wire data in real time
🎤 One-Line Answer for Interview
"Debug LWC by enabling Debug Mode (unminifies code), Chrome DevTools breakpoints, prefixed console.log at lifecycle points, and Salesforce Inspector for wire cache. JSON.stringify proxy objects for readable console output."
Q
Question 74 · 🟠 Intermediate
What is getObjectInfo wire adapter?
✅ Answer
getObjectInfo fetches object metadata — field labels, types, record types, permissions — without Apex. Essential step 1 before getPicklistValues! 📋
import { getObjectInfo } from 'lightning/uiObjectInfoApi'; import ACCOUNT_OBJECT from '@salesforce/schema/Account'; @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT }) objectInfo; // Get default record type Id (for getPicklistValues) get defaultRecordTypeId() { return this.objectInfo?.data?.defaultRecordTypeId; } // All record types for dropdown get recordTypeOptions() { if (!this.objectInfo?.data) return []; return Object.values(this.objectInfo.data.recordTypeInfos) .filter(rt => rt.available && !rt.master) .map(rt => ({ label: rt.name, value: rt.recordTypeId })); } // Field label (for dynamic headers) getFieldLabel(fieldApiName) { return this.objectInfo?.data?.fields[fieldApiName]?.label || fieldApiName; }
🔑 Key Points for Interviewer
  • 🔥getObjectInfo is step 1 before getPicklistValues — provides defaultRecordTypeId
  • 💡objectInfo.data.fields: all accessible fields with label, type, updateable, createable
  • 💡objectInfo.data.recordTypeInfos: all record types with IDs
  • 💡Respects FLS — only returns fields the current user can access
🎤 One-Line Answer for Interview
"getObjectInfo fetches object metadata — field labels, record types, field types, permissions — without Apex. Use to get defaultRecordTypeId before getPicklistValues, build dynamic column headers, or check field-level accessibility."
Q
Question 75 · 🔴 Advanced
How do you implement pagination for large datasets in LWC?
✅ Answer
Use server-side SOQL LIMIT + OFFSET in Apex — never load all records. Fetch one page at a time, show previous/next controls! 📄
// Apex @AuraEnabled(cacheable=true) public static Map<String, Object> getAccountsPage( Integer pageSize, Integer pageNumber) { Integer offset = (pageNumber - 1) * pageSize; return new Map<String, Object>{ 'records' => [SELECT Id, Name FROM Account LIMIT :pageSize OFFSET :offset], 'totalCount' => [SELECT COUNT() FROM Account], 'totalPages' => (Integer)Math.ceil( (Decimal)[SELECT COUNT() FROM Account] / pageSize) }; } // LWC @track records = []; @track currentPage = 1; @track totalPages = 1; pageSize = 50; connectedCallback() { this.loadPage(); } async loadPage() { const r = await getAccountsPage({ pageSize: this.pageSize, pageNumber: this.currentPage }); this.records = r.records; this.totalPages = r.totalPages; } handlePrevious() { if(this.currentPage>1){this.currentPage--;this.loadPage();}} handleNext() { if(this.currentPage<this.totalPages){this.currentPage++;this.loadPage();}}
🔑 Key Points for Interviewer
  • 🔥Never load all records — even if SOQL allows it, rendering crashes the browser
  • 💡SOQL OFFSET has a 2,000 row limit — use keyset pagination for larger datasets
  • 💡Better UX: enable-infinite-loading on lightning-datatable instead of page buttons
  • 💡Always debounce search to prevent excessive Apex calls
🎤 One-Line Answer for Interview
"Server-side pagination uses SOQL LIMIT/OFFSET in Apex — fetch one page at a time (e.g., 50 records), return total count for page controls. SOQL OFFSET has a 2,000 row limit — use keyset pagination (WHERE Id > lastId) for very large datasets."
Q
Question 76 · 🔴 Advanced
What is GraphQL wire adapter in LWC?
✅ Answer
GraphQL wire adapter queries multiple related objects in one call using GraphQL syntax — replaces multiple wire calls and reduces round-trips! 📊
import { gql, graphql } from 'lightning/uiGraphQLApi'; @wire(graphql, { query: gql` query AccountsWithContacts($searchText: String) { uiapi { query { Account( where: { Name: { like: $searchText } } first: 10 orderBy: { Name: { order: ASC } } ) { edges { node { Id Name { value } Contacts { edges { node { Id Name { value } } } } } } } } } } `, variables: '$variables' }) graphqlResult;
🔑 Key Points for Interviewer
  • 🔥GraphQL adapter replaces multiple wire calls — one query for Account + Contacts
  • 💡gql tagged template provides syntax highlighting and validation
  • 💡Variables prefixed with $ are reactive — query re-runs on change
  • 💡Respects FLS and sharing automatically like all LDS adapters
🎤 One-Line Answer for Interview
"GraphQL wire adapter fetches related data across multiple objects in a single query — eliminating multiple wire calls. Variables prefixed with $ are reactive for dynamic filtering. Respects FLS and sharing automatically."
Q
Question 77 · 🔴 Advanced
What are the advanced LWC communication patterns?
✅ Answer
Four patterns: @api (parent→child), CustomEvent (child→parent), LMS (unrelated), Service Module (global state) — each with clear use cases! 📡
PatternDirectionWhen to Use
@api propertyParent → ChildPass data down from parent
@api methodParent → ChildParent calls child method (focus, reset)
CustomEventChild → ParentNotify parent of user action
LMSAny → AnyCross-section, Aura+VF+LWC communication
Service ModuleGlobalComplex shared state — many deep components
// 1. Parent passes config via @api <c-filter-panel config={filterConfig}></c-filter-panel> // 2. Child notifies parent via CustomEvent handleFilterChange(event) { this.filterCriteria = event.detail; this.loadData(); } // 3. LMS for unrelated components publish(this.messageContext, APP_CHANNEL, { action: 'REFRESH' }); // 4. Service module for global state import { setState } from 'c/appState'; setState({ selectedRegion: 'Mumbai' }); // Any subscribed component gets the update
🔑 Key Points for Interviewer
  • 🔥Know all 4 patterns — senior interviews always test which to use when
  • 💡Anti-pattern: LMS for parent-child communication — just use @api and CustomEvent
  • 💡Anti-pattern: deep @api prop drilling (4+ levels) — use LMS or service module
  • 💡LDS wire cache: shared server data synced automatically across all components
🎤 One-Line Answer for Interview
"Master all four LWC communication patterns: @api for parent-to-child, CustomEvent for child-to-parent, LMS for cross-section unrelated components, and service modules for complex global state. Anti-patterns: using LMS for parent-child (overkill) and deep @api prop drilling."
Q
Question 78 · 🟠 Intermediate
How do you implement a reusable search and filter component?
✅ Answer
Debounced input fires CustomEvent with search term to parent → parent calls Apex → passes filtered results back as @api property. Separation of concerns! 🔍
// SearchFilter component export default class SearchFilter extends LightningElement { @api placeholder = 'Search...'; debounceTimer; handleInput(event) { clearTimeout(this.debounceTimer); const term = event.target.value; this.debounceTimer = setTimeout(() => { this.dispatchEvent(new CustomEvent('filter', { detail: { searchTerm: term } })); }, 300); } } // Parent — handles filter, owns data state @track records = []; handleFilter(event) { this.searchKey = event.detail.searchTerm; this.searchRecords(); } async searchRecords() { this.records = await searchAccounts({ searchKey: this.searchKey }); }
🔑 Key Points for Interviewer
  • 🔥Always debounce (300ms) — prevents Apex calls on every keystroke
  • 💡Client-side filtering: .filter() on loaded data — good for <500 records
  • 💡Server-side filtering: call Apex on each debounced search — needed for large data
  • 💡clearTimeout + setTimeout = debounce without needing lodash
🎤 One-Line Answer for Interview
"Reusable search filter debounces input (300ms) and fires CustomEvent with search term. Parent handles event and calls Apex or client-side filters. Debouncing prevents excessive Apex calls on every keystroke."
Q
Question 79 · 🔴 Advanced
How do you implement infinite scroll with IntersectionObserver in LWC?
✅ Answer
Observe a sentinel element at the bottom of the list — IntersectionObserver fires when it enters viewport, load next page! ∞
// HTML — add sentinel at bottom of list // <template for:each={records} for:item="rec"> // <div key={rec.Id}>{rec.Name}</div> // </template> // <div class="sentinel"></div> ← observe this observer; isLoading = false; currentOffset = 0; totalCount = 0; renderedCallback() { const sentinel = this.template.querySelector('.sentinel'); if (sentinel && !this.observer) { this.observer = new IntersectionObserver(entries => { if (entries[0].isIntersecting && !this.isLoading) { this.loadNextPage(); } }, { threshold: 0.5 }); this.observer.observe(sentinel); } } async loadNextPage() { if (this.records.length >= this.totalCount) return; this.isLoading = true; const result = await getNextPage({ offset: this.currentOffset, pageSize: 50 }); this.records = [...this.records, ...result.records]; this.totalCount = result.totalCount; this.currentOffset += 50; this.isLoading = false; } disconnectedCallback() { if (this.observer) this.observer.disconnect(); }
🔑 Key Points for Interviewer
  • 🔥Always disconnect IntersectionObserver in disconnectedCallback
  • 💡Check records.length >= totalCount to stop loading when all records fetched
  • 💡SOQL OFFSET limit: 2,000 rows — use keyset pagination for larger datasets
  • 💡Add isLoading guard to prevent duplicate calls when scrolling fast
🎤 One-Line Answer for Interview
"IntersectionObserver infinite scroll observes a sentinel element at the bottom of the list — when it enters the viewport, load the next page of data. Always disconnect the observer in disconnectedCallback and guard against duplicate loads when scrolling fast."
Q
Question 80 · 🔴 Advanced
What are the top LWC interview topics for a senior Salesforce developer?
✅ Answer
Senior interviews test architecture decisions, not just syntax — when to use which communication pattern, state management at scale, performance, Shadow DOM, and Jest! 🎯
Topic AreaKey Questions
Architecture@wire vs imperative? Which communication pattern when? State at scale?
PerformanceMemoize getters, debounce, lazy loading, Promise.all, LDS cache
Shadow DOMCSS custom properties, querySelector, composition with slots
TestingJest: wire mocks, DOM assertions, event simulation, 80%+ coverage
Real scenariosBuild lookup, datatable inline edit, wizard, modal, CSV export
CommunicationAll 4 patterns and when each — @api, CustomEvent, LMS, Service Module
// 5 questions seniors MUST answer well: // 1. Wire vs imperative? // Wire: reactive read-only | Imperative: DML, conditional, user-triggered // 2. State management across 10+ components? // Parent SSOT | LMS for unrelated | JS service for global // 3. Performance at scale? // Memoize getters | server pagination | lazy render | Promise.all // 4. Shadow DOM for styling and events? // Styling: CSS custom properties | Events: bubbles+composed:true // 5. Jest testing strategy? // createElement | wire mocks | shadowRoot.querySelector // | Promise.resolve | afterEach cleanup
🔑 Key Points for Interviewer
  • 🔥Senior interviews test WHY and WHEN — not just HOW to write LWC syntax
  • 💡Prepare real project examples — interviewers probe for hands-on experience
  • 💡Know the governor limits: SOQL OFFSET 2000, callout timeout 10s, heap 6MB
  • 💡Architecture questions: always discuss tradeoffs — rarely one right answer
🎤 One-Line Answer for Interview
"Senior LWC interviews test architectural judgment — when to use @wire vs imperative, state management at scale, performance patterns, Shadow DOM, and Jest testing strategy. Prepare real project examples demonstrating WHY you made specific decisions."