LWC Interview Questions & Lightning Web Components Guide — SFX Support
Dive into the world of Lightning Web Components (LWC), Salesforce's modern framework for building high-performance UI. This module covers LWC fundamentals, component structure, data flow, event communication, and best practices.
1. Introduction to LWC
Lightning Web Components (LWC) represents Salesforce's modern approach to building user interfaces on the Lightning Platform. Introduced in Spring '19, LWC is a client-side JavaScript framework built on web standards, offering superior performance, a familiar development experience for web developers, and seamless integration with the Salesforce ecosystem.
What is LWC?
LWC is a lightweight, performant framework for building single-page applications and reusable UI components. It leverages modern web standards (ECMAScript 7+, Web Components, Shadow DOM) and provides a thin layer of Salesforce-specific services on top — making LWC development feel much like standard web development.
Why LWC?
- Performance: Built on web standards, LWC is significantly faster than its predecessor Aura Components due to less framework overhead and native browser support.
- Modern Web Standards: Familiarity for web developers, easier to learn, and aligned with industry best practices.
- Reusability: Components are modular and reusable across different parts of your Salesforce org or Experience Cloud sites.
- Security: Leverages
Shadow DOMfor encapsulation, protecting component internals from external interference. - Developer Productivity: Modern tooling, clear component lifecycle, and a streamlined development model.
- Coexistence with Aura: LWC and Aura components can coexist and communicate within the same Lightning page, allowing gradual migration.
LWC is the recommended framework for building new UI components on the Salesforce platform. While Aura components are still supported, LWC offers the most benefits for future development.
2. LWC Core Concepts
At its heart, an LWC is a custom HTML element with its own JavaScript, HTML template, and CSS. These three files work together to define the component's behavior, structure, and styling.
a. Component-Based Architecture
LWC promotes a component-based architecture where UIs are broken down into small, independent, and reusable building blocks. Each component manages its own state and renders its own UI, making development modular and maintainable.
b. HTML Template (.html)
Defines the component's UI structure using standard HTML with special LWC directives. Uses a <template> tag as the root.
<template>
<div class="container">
<h1>Hello, {greeting}!</h1>
<button onclick={handleClick}>Change Greeting</button>
</div>
</template>
{property}— Binds data from the JavaScript class to the template.onclick={methodName}— Binds an event to a JavaScript method.lwc:if,lwc:elseif,lwc:else— Conditional rendering.for:each,for:item,for:index— Iterates over a list to render items.
c. JavaScript Class (.js)
Contains the component's logic, data, and event handlers. It's an ES6 module that imports functionalities from the lwc module.
import { LightningElement, api, track } from 'lwc';
export default class HelloWorld extends LightningElement {
@api greeting = 'World'; // Public property
@track counter = 0; // Reactive private property
connectedCallback() {
console.log('Component is connected to the DOM!');
}
handleClick() {
this.counter++;
this.greeting = 'Salesforce Developer ' + this.counter;
}
}
import { LightningElement } from 'lwc';— Imports the base class for LWC.export default class ... extends LightningElement— Defines the component's JavaScript class.- Decorators (
@api,@track,@wire) — Special annotations that add reactive or public capabilities to properties.
d. CSS (.css)
LWC uses Shadow DOM for CSS encapsulation — styles defined in a component's CSS file are scoped to that component and don't leak out or interfere with other components. This prevents style conflicts in complex applications.
.container {
padding: 20px;
border: 1px solid rgba(255,255,255,.06);
border-radius: 8px;
text-align: center;
}
h1 {
color: #0070d2;
font-size: 2em;
}
button {
background-color: #0070d2;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
3. LWC Component Structure
Every Lightning Web Component resides in a folder with the same name as the component, containing the HTML, JavaScript, metadata, and optionally a CSS file.
Standard Folder Structure
force-app/main/default/lwc/
└── myComponentName/
├── myComponentName.html
├── myComponentName.js
├── myComponentName.css (Optional)
└── myComponentName.js-meta.xml
myComponentName.html— Defines the component's user interface.myComponentName.js— Contains the component's logic, properties, and event handlers.myComponentName.css— Optional scoped styles (Shadow DOM encapsulated).myComponentName.js-meta.xml— Defines API version, exposure targets, and object restrictions.
Example js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Account</object>
<object>Contact</object>
</objects>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
apiVersion— Specifies the API version for the component.isExposed— Set totrueto make the component available in Lightning App Builder or Experience Builder.targets— Defines where the component can be used (App Page, Record Page, Home Page, etc.).targetConfigs— Provides specific configurations per target, such as restricting usage to certain sObjects on a record page.
4. Data Flow & Decorators (@api, @track, @wire)
LWC uses decorators to enable reactivity and public properties in JavaScript classes. These are imported from the lwc module and play a crucial role in how data flows within and between components.
a. @api — Public Properties
- Purpose: Exposes a property as public. Parent components can set its value on a child component. Changes are reactive.
- Use Cases: Passing data down from parent to child component.
import { LightningElement, api } from 'lwc';
export default class ChildComponent extends LightningElement {
@api recordId;
@api message = 'Default Message';
}
<c-child-component record-id="001..." message="Hello from Parent"></c-child-component>
b. @track — Reactive Private Properties
- Purpose: Makes a private property reactive so template re-renders when its value changes.
- Important: As of Winter '20, all fields are reactive by default. Only use
@trackfor objects or arrays when you need reactivity on their internal property changes (not just reference changes).
import { LightningElement, track } from 'lwc';
export default class MyComponent extends LightningElement {
@track myObject = { name: 'Initial', value: 0 };
updateObject() {
// Best practice: reassign to trigger reactivity clearly
this.myObject = { ...this.myObject, value: this.myObject.value + 1 };
}
}
c. @wire — Reactive Data Service
- Purpose: Invokes an Apex method or UI API wire adapter and provisions data to a property or function. Automatically re-renders when underlying data changes.
- Use Cases: Fetching records, getting picklist values, querying Apex methods reactively.
import { LightningElement, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
export default class AccountList extends LightningElement {
@wire(getAccounts)
accounts; // { data, error } auto-provisioned
// Or use a function for more control:
// @wire(getAccounts)
// wiredAccounts({ error, data }) {
// if (data) { this.accounts = data; }
// else if (error) { this.error = error; }
// }
}
5. Event Communication
Components in LWC communicate primarily through events. Understanding event flow is crucial for building interactive applications where components need to interact with each other.
a. Child-to-Parent Communication (Custom Events)
Children dispatch custom events; parents listen for them. This is the recommended way for a child to notify its parent of an action or data change.
import { LightningElement } from 'lwc';
export default class ChildComponent extends LightningElement {
handleClick() {
const myCustomEvent = new CustomEvent('childclick', {
detail: { message: 'Hello from child!' },
bubbles: true,
composed: true
});
this.dispatchEvent(myCustomEvent);
}
}
<template>
<p>Message from child: {childMessage}</p>
<c-child-component onchildclick={handleChildClick}></c-child-component>
</template>
import { LightningElement, track } from 'lwc';
export default class ParentComponent extends LightningElement {
@track childMessage = '';
handleChildClick(event) {
this.childMessage = event.detail.message;
}
}
b. Parent-to-Child Communication
Parents pass data down to children using @api public properties — covered in the decorators section above.
c. Unrelated Components — Lightning Message Service (LMS)
For components not in a direct parent-child relationship (e.g., on different parts of a Lightning page), Lightning Message Service (LMS) is the recommended solution using a publish-subscribe model.
import { LightningElement, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import SAMPLE_MESSAGE_CHANNEL from '@salesforce/messageChannel/SampleMessageChannel__c';
export default class PublisherComponent extends LightningElement {
@wire(MessageContext) messageContext;
publishMessage() {
const payload = { recordId: '001ABC', message: 'Data updated!' };
publish(this.messageContext, SAMPLE_MESSAGE_CHANNEL, payload);
}
}
import { LightningElement, wire } from 'lwc';
import { subscribe, unsubscribe, MessageContext } from 'lightning/messageService';
import SAMPLE_MESSAGE_CHANNEL from '@salesforce/messageChannel/SampleMessageChannel__c';
export default class SubscriberComponent extends LightningElement {
subscription = null;
receivedMessage = '';
@wire(MessageContext) messageContext;
connectedCallback() {
this.subscription = subscribe(
this.messageContext,
SAMPLE_MESSAGE_CHANNEL,
(message) => { this.receivedMessage = message.message; }
);
}
disconnectedCallback() {
unsubscribe(this.subscription);
this.subscription = null;
}
}
6. LWC Lifecycle Hooks
LWC components have a well-defined lifecycle, and you can tap into specific phases using lifecycle hooks — methods in the component's JavaScript class that execute at particular moments.
constructor()— Called when the component is created. Always callsuper()first. Do not access@apiproperties orthis.templatehere — they are not yet initialized. Use for initializing private properties.connectedCallback()— Called when the component is inserted into the DOM. Use for fetching initial data (if not using@wire), setting up event listeners, and accessing@apiproperties. Fires again if component is removed and reinserted.renderedCallback()— Called after every render (initial and re-renders). Use for DOM manipulations or integrating third-party libraries. Be cautious — avoid updating reactive properties here without a guard condition to prevent infinite loops.disconnectedCallback()— Called when the component is removed from the DOM. Use for cleanup: removing event listeners, unsubscribing from LMS to prevent memory leaks.errorCallback(error, stack)— Called when an error occurs in any descendant component. Acts like a JavaScript error boundary. Use for catching/logging errors and displaying fallback UI.
import { LightningElement, api } from 'lwc';
export default class LifecycleDemo extends LightningElement {
@api recordId;
constructor() {
super();
console.log('1. Constructor called');
// Do NOT access this.recordId or this.template here
}
connectedCallback() {
console.log('2. Connected Callback — Record ID:', this.recordId);
}
renderedCallback() {
console.log('3. Rendered Callback');
// Guard against infinite loops:
// if (!this.hasRendered) { this.hasRendered = true; ... }
}
disconnectedCallback() {
console.log('4. Disconnected Callback — clean up resources');
}
errorCallback(error, stack) {
console.error('5. Error Callback:', error, stack);
}
}
7. Working with Apex in LWC
LWC can interact with Salesforce data and logic stored in Apex classes — allowing complex database operations, business logic, and callouts not possible in client-side JavaScript alone.
a. Apex Method Requirements
Apex methods called by LWC must be public static and annotated with @AuraEnabled. Read-only methods should also use cacheable=true for caching and performance.
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List<Account> getAccounts() {
return [SELECT Id, Name, Industry FROM Account LIMIT 10];
}
@AuraEnabled
public static String createAccount(String accountName) {
Account newAcc = new Account(Name = accountName);
insert newAcc;
return newAcc.Id;
}
}
b. Calling Apex Imperatively
Use imperative calls when you need explicit control over when the Apex method executes (e.g., on a button click). Returns a Promise.
import { LightningElement } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
import createAccount from '@salesforce/apex/AccountController.createAccount';
export default class ApexImperativeDemo extends LightningElement {
accounts;
error;
handleLoadAccounts() {
getAccounts()
.then(result => {
this.accounts = result;
this.error = undefined;
})
.catch(error => {
this.error = error;
this.accounts = undefined;
});
}
handleCreateAccount() {
createAccount({ accountName: 'New Imperative Account' })
.then(result => {
console.log('Account created with ID:', result);
this.handleLoadAccounts(); // Refresh the list
})
.catch(error => {
this.error = error;
});
}
}
c. Calling Apex with @wire (Reactive)
Use @wire for reactive data — it automatically refreshes when reactive parameters change. The $ prefix makes a parameter reactive.
import { LightningElement, api, wire } from 'lwc';
import getAccountDetails from '@salesforce/apex/AccountController.getAccountDetails';
export default class AccountDetailsWire extends LightningElement {
@api recordId;
@wire(getAccountDetails, { accountId: '$recordId' }) // '$' makes it reactive
wiredAccount({ error, data }) {
if (data) {
this.account = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.account = undefined;
}
}
}
Choose imperative when you need controlled execution (button click, DML). Choose @wire for read-only reactive data that should auto-refresh.
8. LWC Best Practices
Adhering to best practices ensures your Lightning Web Components are performant, maintainable, secure, and reusable.
a. Component Granularity
- Small, Focused Components: Design components to do one thing well. Break complex UIs into smaller, reusable child components.
- Container vs. Presentational: Container components handle data fetching and logic; presentational components focus solely on rendering UI based on input properties.
b. Data Flow & Reactivity
- Unidirectional Data Flow: Data flows down via
@api; events flow up via custom events. - Minimize
@track: Only use for objects/arrays needing internal property reactivity. Primitives are auto-reactive. - Prefer
@wirefor Read-Only Data: Use imperative Apex only for DML or when explicit execution control is needed.
c. Performance
- Lazy Loading: Load data or components only when needed.
- Minimize DOM Manipulation: Let the framework handle DOM updates. Avoid
this.template.querySelectorwithout guards inrenderedCallback. - Optimize Apex Calls: Ensure Apex methods are bulkified and selective to avoid governor limits.
d. Security
- Respect FLS & CRUD: LWC respects FLS and CRUD for UI API calls by default. In Apex, use
with sharingand always manually check FLS and CRUD for sensitive data. - Sanitize User Input: Always sanitize input before using it in queries or DML operations.
e. Reusability & Maintainability
- Clear Naming Conventions: Use consistent, descriptive names for components, properties, methods, and events.
- Comments: Document complex logic and non-obvious parts of the code.
- Use Lightning Base Components: Utilize standard
lightning-components (e.g.,lightning-button,lightning-input,lightning-datatable) — they are performant, accessible, and SLDS-compliant.
f. Testing
- Write Jest Unit Tests: Use Jest for client-side LWC unit testing to verify component behavior in isolation.
- Test Apex Integration: Ensure all Apex methods called by LWC are covered by Apex unit tests.
9. Conclusion & Next Steps
Lightning Web Components provide a powerful, modern, and efficient framework for building rich user interfaces on the Salesforce platform. By embracing web standards and offering robust features for data binding, event communication, and Apex integration, LWC empowers developers to create highly performant and maintainable applications.
Key Takeaways
- Web Standards First: LWC is built on modern web standards, making it approachable for web developers.
- Component-Based: Focus on building small, reusable, and encapsulated components.
- Decorators are Key: Understand
@apifor public properties,@trackfor reactive objects/arrays, and@wirefor reactive data provisioning. - Event-Driven Communication: Use custom events for child-to-parent and LMS for unrelated component communication.
- Lifecycle Awareness: Leverage lifecycle hooks to execute code at specific points in a component's life.
- Apex Integration: Seamlessly connect to Apex for server-side logic and data operations.
- Best Practices: Prioritize performance, security, reusability, and thorough testing.
Mastering LWC is essential for any Salesforce developer looking to build modern, engaging, and scalable user experiences. Continue exploring the official Salesforce LWC Developer Guide and Trailhead modules for deeper dives into advanced topics and real-world scenarios.