Top 40 Salesforce LWC Interview Questions & Answers 2025 — Lightning Web Components Deep Dive with Real Code Examples
Salesforce LWC Interview Questions — Lightning Web Components Explained
Decorators, Lifecycle Hooks, Wire, LMS, Navigation, LDS, Events & Real Code — Real Scenarios Explained for Interviews
| Feature | LWC | Aura (Old) |
|---|---|---|
| Based on | Web Standards (HTML/JS/CSS) | Proprietary Aura framework |
| Performance | Fast — native browser APIs | Slower — heavy abstraction |
| Learning Curve | Easy — standard JS | Steeper — Aura-specific syntax |
| Embed Aura in LWC? | ❌ No | ✅ Can embed LWC inside Aura |
| Future | ✅ Actively developed | ⚠️ Legacy — no new features |
- ✅Chrome DevTools Performance tab — LWC shows fewer scripting cycles
- ✅Network tab — LWC JS bundle sizes are noticeably smaller
- ✅Re-renders on filter change are instant in LWC vs visible lag in Aura
- ✅On mobile (Experience Cloud), LWC is significantly smoother
At XYZ Company, the old Quote summary Aura component was rebuilt as LWC. The LWC version loaded in ~400ms vs ~900ms for Aura on the same machine — measurable in browser DevTools. Re-render on currency toggle was instant in LWC vs 200–300ms lag in Aura.
<template> tag and uses Salesforce directives for data binding, loops, and conditionals! 🖼️| Feature | Details |
|---|---|
| Root tag | Must be <template> |
| Data binding | {propertyName} — one-way from JS to HTML |
| Conditionals | lwc:if / lwc:else (new) or if:true / if:false (old) |
| Loops | for:each + key, or iterator |
| Child components | <c-child-component> |
| Slots | <slot> — lets parent inject content |
- 🔥HTML file is mandatory — every LWC must have it
- 💡Template expressions {value} are read-only — no JS expressions like {a + b}
- 💡Shadow DOM is applied automatically — styles are encapsulated per component
- 💡HTML holds UI structure only — business logic belongs in the JS file
| Responsibility | Details |
|---|---|
| Class definition | export default class MyComp extends LightningElement |
| Properties | Reactive state via @track, @api |
| Wire adapters | @wire for declarative Salesforce data |
| Event handlers | Methods bound to HTML events |
| Lifecycle hooks | connectedCallback, renderedCallback, etc. |
| Apex calls | Imperative or wire-based |
- 🔥Must use export default class ... extends LightningElement
- 💡Every public method/property exposed via @api lives here
- 💡JS file and HTML file must share the same component name
- 💡UI layout belongs in HTML — business logic belongs here
| Decorator | Purpose | Reactive? | Public? |
|---|---|---|---|
| @api | Expose property/method publicly to parent | ✅ Yes | ✅ Yes |
| @track | Deep reactivity for objects and arrays | ✅ Yes (deep) | ❌ No |
| @wire | Declarative Salesforce data fetching | ✅ Reactive to $ params | ❌ No |
- 🔥@api + @track = compile error. @api + @wire = compile error
- 💡@api already makes a property reactive — @track is redundant on it
- 💡Since Spring '20, primitives are auto-reactive — @track is only needed for nested objects/arrays
- ✅Makes property accessible from parent components or App Builder
- ✅Any change from parent triggers child re-render
- ❌Child should NOT mutate @api properties — one-way flow only
- ✅Makes LWC deeply reactive — nested property changes trigger re-render
- 💡Since Spring '20, primitives are auto-reactive — @track mainly needed for objects/arrays
- ❌Internal use only — parent cannot access @track properties
- ✅Auto-fetches Salesforce data and re-fetches when $ reactive params change
- ✅Returns {data, error} — always handle both cases
- ❌Requires cacheable=true on the Apex method — DML not allowed
| Scenario | Wire Executes |
|---|---|
| No reactive property ($) | Once — on component initialization |
| Reactive property changes 3 times | 3 times — once per change |
| Reactive property is undefined | Wire does NOT fire ❌ |
| User navigates away and back | Re-executes on re-initialization |
- 🔥Wire is part of the reactive engine of LWC — you don't call it manually
- 💡Wire fires before connectedCallback() in the lifecycle
- 💡Wire does NOT re-execute if a non-reactive (no $) parameter changes
| Factor | @wire | connectedCallback() |
|---|---|---|
| Triggered by | Framework (reactive params) | DOM insertion |
| Called automatically? | ✅ Yes | ✅ Yes |
| Purpose | Fetch & sync Salesforce data | Run JS logic on mount |
| Reactive? | ✅ Re-runs on param change | ❌ Runs once only |
| Fires first? | ✅ Wire fires BEFORE connectedCallback | Fires AFTER wire |
| DML via Apex? | ❌ Not allowed | ✅ Allowed (imperative) |
- ✅Subscribe to LMS channel
- ✅Add DOM event listeners
- ✅Set default/initial values
- ✅Conditional data fetch based on @api value (if/else logic)
- ❌Don't query DOM here — DOM not yet rendered in connectedCallback
| Hook | When It Fires | Common Use |
|---|---|---|
| constructor() | Component class instantiated | Initialize local variables, call super() |
| connectedCallback() | Component inserted into DOM | LMS subscribe, set defaults, imperative logic |
| renderedCallback() | After every render/re-render | DOM manipulation — use flag to prevent loops |
| disconnectedCallback() | Component removed from DOM | Unsubscribe LMS, clean up listeners |
| errorCallback(error, stack) | Child component throws error | Error boundary — parent only |
- 🔥renderedCallback fires on EVERY re-render — always use a boolean flag to prevent infinite loops
- 🔥constructor — always call super() as first line — mandatory
- 💡errorCallback — only fires in parent components when a child throws — like a try-catch at component tree level
- 💡Always pair connectedCallback (subscribe) with disconnectedCallback (unsubscribe) to prevent memory leaks
| Need | Solution |
|---|---|
| Data on page load reactively | Use @wire at class level ✅ |
| Data on page load imperatively | Use imperative Apex in connectedCallback ✅ |
| Data on button click | Use imperative Apex in event handler ✅ |
| Conditional data fetch | Imperative Apex with if/else logic ✅ |
| Feature | Details |
|---|---|
| Communication Scope | Entire App Page — unrelated components |
| Works across | LWC ↔ LWC, LWC ↔ Aura, LWC ↔ Visualforce |
| Channel defined in | .messageChannel metadata file |
| Subscribe | subscribe() in connectedCallback |
| Unsubscribe | unsubscribe() in disconnectedCallback |
| Publish | publish() from any component |
- 🔥LMS replaced the old Pub-Sub community pattern — always recommend LMS in interviews
- 💡APPLICATION_SCOPE = truly global across entire app page
- 💡Always unsubscribe in disconnectedCallback to prevent memory leaks
- ⚠️Does NOT work across different browser tabs or pages
| Aura Concept | Scope | LWC Equivalent |
|---|---|---|
| Component Event | Bubbles up parent-child only | Custom Events + dispatchEvent |
| Application Event | Broadcasts to ALL handlers in app | Lightning Message Service (LMS) |
- 🔥Application Events are an anti-pattern in large orgs — hard to debug, LMS replaced them
- 💡LWC intentionally removed Application Events — forced developers to use LMS
- 💡Custom Events with bubbles:true + composed:true can cross shadow DOM boundaries
lightning/platformShowToastEvent — fire it as a custom event using dispatchEvent()! 🍞| Variant | Icon | Use Case |
|---|---|---|
| success | ✅ Green checkmark | Record saved, action completed |
| error | ❌ Red X | Apex error, validation failure |
| warning | ⚠️ Yellow warning | Partial success or caution |
| info | ℹ️ Blue info | General information |
- 🔥Works on Record Pages, App Pages, Home Pages — does NOT work in Utility Bar or Experience Cloud
- 💡mode: 'sticky' keeps toast until user dismisses — use for important messages
- 💡Toast is fired as an event — not a method call
- 🔥Property name must be exactly recordId — case-sensitive
- 💡Use CloseActionScreenEvent to programmatically close the Quick Action modal
- 💡@api objectApiName also auto-populates with the object type
- ⚠️If launched from Global Quick Action (no record), recordId will be undefined
| Direction | Method |
|---|---|
| Parent → Child (data) | @api property — HTML attribute binding |
| Parent → Child (method) | @api method — parent calls via querySelector |
| Child → Parent | CustomEvent + dispatchEvent |
| Unrelated components | LMS (Lightning Message Service) |
- 🔥camelCase in JS → kebab-case in HTML — always! (accountName → account-name)
- 💡Child should NEVER mutate @api properties — read-only from child's perspective
- 💡Data flows DOWN via @api, events flow UP via CustomEvent — this is the core LWC pattern
- 🔥Event name in JS: camelCase (accountSelect) → Listener in HTML: lowercase prefixed with on (onaccountselect)
- 🔥Child CANNOT do this.parent.someMethod() — no parent reference exists in LWC
- 💡Use event.detail to pass payload to parent — can be any serializable data
- 💡This is by design — LWC enforces encapsulation. Tight coupling between parent/child is an anti-pattern
lightning/navigation — the component class must extend NavigationMixin(LightningElement)! 🧭| Destination | type value |
|---|---|
| Record Page | standard__recordPage |
| Object List View | standard__objectPage |
| New Record Form | standard__objectPage + actionName: 'new' |
| External URL | standard__webPage |
| App Page (Nav Item) | standard__navItemPage |
- 🔥NavigationMixin is a Higher Order Component (HOC) pattern — wraps LightningElement
- 💡NavigationMixin.GenerateUrl builds a URL without navigating — useful for anchor tags
- ❌Never use window.location.href — breaks Salesforce SPA navigation
- 💡Works only in Lightning Experience — not in Classic
| Factor | render() | lwc:if directive |
|---|---|---|
| Use case | Completely different templates | Show/hide within same template |
| How many HTML files | Multiple .html files in bundle | Single HTML file |
| Complexity | More setup needed | Simple and inline |
| When to use | Error state vs Success state vs Loading | Most conditional rendering |
- 🔥render() is called by the framework — never call this.render() manually
- 💡Additional HTML files must be in the same component folder
- 💡render() is rarely needed — lwc:if covers most use cases. Use render() for completely different layout structures only
| Module | Returns |
|---|---|
| @salesforce/user/Id | Current logged-in User's ID |
| @salesforce/user/Name | User's Name |
| @salesforce/user/IsGuest | Boolean — is guest user? |
| @salesforce/userPermission/PermName | Boolean — has permission? |
| @salesforce/customPermission/PermName | Boolean — has custom permission? |
| Factor | @track | @api |
|---|---|---|
| Scope | Internal — private to component | Public — accessible from outside |
| Purpose | Deep reactivity for objects/arrays | Expose to parent or App Builder |
| Accessible from parent? | ❌ No | ✅ Yes |
| Mutable inside component? | ✅ Yes freely | ⚠️ Avoid — anti-pattern |
| Needed for primitives? | ❌ No — auto-reactive since Spring '20 | ❌ Already reactive |
In the Quote Line Items LWC: @api recordId received the Quote ID from the parent record page (public). @track lineItems = [] tracked the array reactively as rows were added/removed. @wire(getQuoteLineItems, { quoteId: '$recordId' }) fetched line items reactively when the record changed.
| Factor | cacheable=true | cacheable=false (default) |
|---|---|---|
| Use with @wire? | ✅ Yes — required | ❌ No — use imperative |
| Cache result? | ✅ Yes — client cache | ❌ No |
| DML operations? | ❌ NOT allowed | ✅ Allowed |
| Performance | ✅ Faster — cached | Hits server every time |
| Refresh stale cache | refreshApex() needed after DML | Always fresh |
| Scenario | Deployable? |
|---|---|
| No CSS file at all | ✅ Yes |
| Empty CSS file | ✅ Yes |
| CSS with valid rules | ✅ Yes |
| CSS with syntax errors | ❌ No — compile error |
- 🔥Only HTML and JS files are mandatory in an LWC bundle — CSS, meta XML, SVG are all optional
- 💡LWC uses Shadow DOM — CSS in the file is scoped to the component only. Styles never leak out
- 💡Cannot use global selectors like body or div to style outside the component
| Aura | LWC Equivalent |
|---|---|
| force:createRecord | NavigationMixin + actionName: 'new' |
| force:editRecord | NavigationMixin + actionName: 'edit' |
| force:navigateToRecord | NavigationMixin + standard__recordPage |
| Factor | Wire | Imperative |
|---|---|---|
| Triggered by | Framework automatically | You explicitly (button, event) |
| cacheable=true required? | ✅ Yes — mandatory | ❌ No — optional |
| DML allowed? | ❌ No | ✅ Yes |
| Reactive? | ✅ Re-runs on $ param change | ❌ No |
| Returns | {data, error} object | Promise |
| Requirement | Imperative Apex | Wire Adapter |
|---|---|---|
| @AuraEnabled | ✅ Required | ✅ Required |
| cacheable=true | ❌ Optional | ✅ MANDATORY |
| DML allowed? | ✅ Yes | ❌ No |
| Returns | Promise | {data, error} |
| Called by | You explicitly | Framework automatically |
| Reactive? | ❌ No | ✅ Yes |
getQuoteLineItems was marked cacheable=true → used with @wire for display. saveQuoteLineItem was NOT cacheable → called imperatively on Save button since it performed DML. Two different methods, two different approaches — zero conflict. After DML, refreshApex() was called to refresh the wire cache.
| Factor | Wire | Imperative |
|---|---|---|
| Trigger | Framework auto-manages | Developer manually triggers |
| cacheable=true | ✅ Required | ❌ Not required |
| DML | ❌ Not allowed | ✅ Allowed |
| Reactive to params | ✅ Yes — $ prefix | ❌ No |
| Return type | {data, error} | Promise |
| Best for | Read on page load, reactive filters | Save, delete, user-triggered actions |
| Error handling | error property in result | .catch() or try/catch |
| Factor | Wire Property | Wire Function |
|---|---|---|
| Syntax | @wire(fn, params) propName; | @wire(fn, params) handlerFn({data, error}) |
| Logic on receive? | ❌ No | ✅ Yes — full JS logic |
| refreshApex? | ✅ Pass property directly | ✅ Save result reference first |
| Best for | Simple display in template | Transform, validate, set multiple properties |
- 🔥Wire = read-only data fetching — never DML. This is the #1 rule of wire
- 💡DML always goes through imperative Apex calls
- 💡After imperative DML, use refreshApex() to refresh the wire cache
| Feature | LDS |
|---|---|
| Requires Apex? | ❌ No |
| Respects FLS & Sharing? | ✅ Automatically |
| Client-side cache? | ✅ Yes — shared across components |
| Auto-updates all components? | ✅ Yes — same record |
- 🔥Schema imports (@salesforce/schema) = compile-time validation — typos in field names caught at deploy time, not runtime
- 💡getFieldValue = raw value. getFieldDisplayValue = formatted value (e.g., ₹1,000.00 for currency)
- 💡createRecord/updateRecord/deleteRecord return Promises — use async/await or .then()
- 💡LDS cache auto-updates when any component creates/updates/deletes — all components showing that record refresh instantly
| Scenario | Use |
|---|---|
| Simple single record create/update/delete | ✅ LDS |
| FLS must be enforced automatically | ✅ LDS (auto-enforced) |
| LDS cache auto-update needed | ✅ LDS |
| Multiple objects in one transaction | ✅ Apex |
| Callout after DML | ✅ Apex |
| Complex validation beyond field rules | ✅ Apex |
| Bulk insert/update (multiple records) | ✅ Apex |
| Cross-object operations | ✅ Apex |
LDS used → Updating a single Account's KYC status field from a Quick Action — simple field update, FLS auto-enforced, zero Apex written, cache auto-updated. Apex used → Creating a Proforma Invoice — required inserting the invoice record, linking line items, generating PDF, and triggering a BC API callout, all in one transaction. LDS cannot do any of that.
lightning/uiObjectInfoApi — first wire getObjectInfo for the recordTypeId, then use it reactively in getPicklistValues. Zero Apex needed! 📋- 🔥Spread creates a shallow copy — nested objects are still referenced
- 💡For deep copy → use JSON.parse(JSON.stringify(obj))
- 💡Spread is the standard way to update @track arrays/objects immutably in LWC
- 💡Creating a new array/object reference with spread triggers @track reactivity
| Method | Reliability | Use Case |
|---|---|---|
| document.getElementById() | ❌ Never works | Don't use in LWC |
| this.template.querySelector('#id') | ⚠️ Fragile — id transforms | Avoid |
| this.template.querySelector('[data-id="x"]') | ✅ Reliable | Standard pattern |
| this.template.querySelector('.class') | ✅ Reliable | Class-based selection |
| this.refs.refName (lwc:ref) | ✅ Best | Modern recommended |
- 🔥Shadow DOM = document methods are blind to component's DOM — this is the core reason
- 💡LWC intentionally transforms id attributes at runtime to prevent global id conflicts — so even querySelector('#id') is unreliable
- 💡Never query DOM in constructor() — DOM not ready yet. Safe in renderedCallback() or event handlers
- 💡lwc:ref + this.refs is newer (Summer '23) — know both approaches
| Feature | LMS (Lightning Message Service) | Pub-Sub (Old Pattern) |
|---|---|---|
| Official Salesforce? | ✅ Yes — standard feature | ❌ No — community utility |
| Works across | LWC, Aura, Visualforce | LWC only |
| Setup | Message Channel metadata file | Import pubsub.js module |
| Survives navigation? | ✅ Yes (APPLICATION_SCOPE) | ❌ No |
| Memory management | Auto-managed with unsubscribe | Manual cleanup required |
| Recommended today? | ✅ Yes — always use LMS | ❌ Deprecated |
- 🔥Always recommend LMS in interviews — Pub-Sub is legacy and deprecated
- 💡APPLICATION_SCOPE = truly global across the entire app page, not just a region
- 💡LMS Message Channel is a metadata file deployed to the org — not just a JS utility
| Factor | Event Capturing | Event Bubbling |
|---|---|---|
| Direction | Top → Down (document to target) | Bottom → Up (target to document) |
| Phase | Phase 1 | Phase 3 |
| Default? | ❌ Must opt in | ✅ Default behavior |
| Common in LWC? | Rare | ✅ Very common |
- 🔥99% of interview questions refer to bubbling — capturing is rarely used
- 💡composed: true in LWC is needed to bubble across shadow DOM boundaries between components
- 💡stopPropagation() stops bubbling. stopImmediatePropagation() also stops other listeners on the same element
| Method | What It Stops | Common Use in LWC |
|---|---|---|
| event.stopPropagation() | Event bubbling up the DOM tree | Delete button inside a row — prevent row click from also firing |
| event.preventDefault() | Default browser behavior | Anchor click → use NavigationMixin instead of page reload |
| event.stopImmediatePropagation() | Bubbling + other listeners on same element | Complete isolation of event |
In the Quote Line Items LWC, the delete icon inside each row used stopPropagation() to prevent the row click (which opened a detail panel) from also firing when the delete button was clicked — two separate actions, one event.
- 🔥This pattern is called Service Component in Salesforce docs — no HTML, no default class export
- 💡It's the LWC equivalent of a utility/helper class in Apex
- 💡Import using named exports: import { fn1, fn2 } from 'c/lwcUtils'
- 💡No instantiation needed — pure function imports
- 🔥Must store the wire result in a property to use refreshApex — passing this.wiredFunction doesn't work
- 💡Import from '@salesforce/apex' — not from 'lwc'
- 💡Common pattern: save (DML) → refreshApex (sync cache) → show toast
- 💡refreshApex on a non-wire property = no effect
In the Quote Line Items LWC, after saveLineItem DML, refreshApex(this.wiredLineItems) was called — the line items list auto-refreshed without any page reload, showing the newly added item immediately.
- 🔥SOQL OFFSET limit = 2,000 rows max — for datasets beyond that, use cursor-based pagination (last ID query)
- 💡For better UX on very large sets, lightning-datatable with enable-infinite-loading is cleaner than page buttons
- 💡Always debounce search in pagination components to prevent excessive Apex calls
- ❌Never load all 20,000 records — even if SOQL allows it, rendering will crash the browser
| Scenario | Use |
|---|---|
| Call B needs data from Call A | async/await sequential |
| Calls are independent | Promise.all() — parallel, faster |
| 5+ related data needed on load | Single wrapper Apex method returning Map |
| 20+ chains needed | Suggest Research / redesign — architecture problem |
- 🔥The best answer for "large number of chains" is redesign — consolidate into a single wrapper Apex method
- 💡Promise.all() runs calls in parallel — significantly faster than sequential for independent data
- 💡Never loop with await inside forEach — use for...of loop for sequential async iteration
- 💡Single catch() or try/catch covers the entire chain — clean error handling