The answer is CustomEvent — but interviewers ask follow-ups about bubbling, payload shape, listener syntax, and when to switch to Lightning Message Service. A complete answer covers all four.
The 60-second answer
Use a custom DOM event: in the child, this.dispatchEvent(new CustomEvent('eventname', { detail: payload })). In the parent’s template, listen with <c-child oneventname={handleEvent}></c-child> — the prefix is on + lowercase event name. Read the payload in the handler via event.detail. Event names must be lowercase with no hyphens. This is the right pattern for parent ↔ child communication. For sibling components or cross-DOM communication, switch to Lightning Message Service (LMS).
The mechanic
Child — dispatch the event
// childComponent.js
import { LightningElement } from 'lwc';
export default class ChildComponent extends LightningElement {
handleClick() {
const payload = { id: '001xx000003DGb2', name: 'Acme Corp', amount: 50000 };
const evt = new CustomEvent('selectaccount', {
detail: payload
// bubbles: false (default), composed: false (default)
});
this.dispatchEvent(evt);
}
}
<!-- childComponent.html -->
<template>
<button onclick={handleClick}>Select Acme</button>
</template>
Parent — listen and read the payload
<!-- parentComponent.html -->
<template>
<c-child-component onselectaccount={handleAccountSelected}></c-child-component>
<p>Selected: {selectedName}</p>
</template>
// parentComponent.js
import { LightningElement, track } from 'lwc';
export default class ParentComponent extends LightningElement {
selectedName;
handleAccountSelected(event) {
const { id, name, amount } = event.detail;
this.selectedName = name;
// do something with id / amount
}
}
Rules interviewers test for
| Rule | Why it matters |
|---|---|
Event name lowercase, no hyphens (selectaccount, not select-account) | LWC framework requirement; HTML attribute names are case-insensitive |
Listener prefix is on + eventname | <c-child onselectaccount>; not onSelectAccount |
Payload goes in detail | Always — never a top-level property |
bubbles: false by default | Event stops at the parent unless you opt in |
composed: false by default | Event doesn’t cross Shadow DOM unless you opt in |
Don’t mutate detail after dispatch | Reactivity is one-way; pass a shallow clone for safety |
When to opt into bubbling
Set bubbles: true when the event needs to reach a grandparent, not just the direct parent:
this.dispatchEvent(new CustomEvent('selectaccount', {
detail: payload,
bubbles: true,
composed: false // still confined to LWC DOM tree
}));
composed: true lets the event cross Shadow DOM boundaries — needed if you have nested Aura wrapping LWC, or if the listener is outside the component’s shadow root. Use sparingly; it can leak events to unintended listeners.
When to switch to Lightning Message Service (LMS)
Use LMS when the components are not in a parent-child hierarchy — sibling, in different sections of the same page, or in a Utility Bar.
// senderComponent.js
import { publish, MessageContext } from 'lightning/messageService';
import RECORD_SELECTED_CHANNEL from '@salesforce/messageChannel/RecordSelected__c';
import { LightningElement, wire } from 'lwc';
export default class SenderComponent extends LightningElement {
@wire(MessageContext) messageContext;
handleSelect(id) {
publish(this.messageContext, RECORD_SELECTED_CHANNEL, { recordId: id });
}
}
// receiverComponent.js
import { subscribe, MessageContext } from 'lightning/messageService';
import RECORD_SELECTED_CHANNEL from '@salesforce/messageChannel/RecordSelected__c';
export default class ReceiverComponent extends LightningElement {
@wire(MessageContext) messageContext;
subscription = null;
connectedCallback() {
this.subscription = subscribe(
this.messageContext,
RECORD_SELECTED_CHANNEL,
(msg) => { this.handle(msg); }
);
}
}
LMS works across Aura/LWC and across Utility Bar/Main Region. CustomEvent doesn’t.
Decision matrix
| Communication direction | Right tool |
|---|---|
| Child → Parent | CustomEvent (no bubbling needed) |
| Child → Grandparent | CustomEvent with bubbles: true |
| Parent → Child | @api public property or method on the child |
| Sibling → Sibling | Lightning Message Service |
| LWC ↔ Aura | LMS, or pubsub library if pre-LMS |
| Cross-page tab | LMS |
Anti-patterns
new Event('name')instead ofnew CustomEvent('name', { detail })— vanillaEventhas no payload mechanism.- Camel-case event name —
'selectAccount'works in dev but breaks the framework attribute parser; always lowercase. - Listener as a property assignment (
this.template.querySelector('c-child').onselectaccount = handler;) — works but kills LWC’s declarative reactivity. Use template syntax. - Using
pubsub.js— the old Aura-era library. Salesforce now recommends LMS for everything non-parent-child. Don’t suggest pubsub in a 2026 interview. - Storing huge payloads in
detail— passes by reference; mutating downstream confuses the source. Pass a JSON clone for anything you can’t control.
How to answer in 30 seconds
“A custom DOM event — child does dispatchEvent(new CustomEvent('eventname', { detail: payload })); parent listens declaratively with onevenname={handler} in the template. Event names are lowercase, payload goes in detail. For non-parent-child communication use Lightning Message Service.”
How to answer in 2 minutes
Walk the dispatch + listen mechanic, hit the four interview rules (lowercase name, on prefix, detail payload, bubbling default off), explain the bubbles/composed flags for crossing component or Shadow DOM boundaries, and finish with the decision matrix that picks CustomEvent vs @api vs LMS.
Likely follow-up questions
- What’s the difference between
bubbles: trueandcomposed: true? - How would you pass data from parent to child?
- When would you use Lightning Message Service instead?
- Can a child component call a method on its parent?
- What happens to events when the component is destroyed?
Verified against: LWC Developer Guide — Communicate with Events, Lightning Message Service. Last reviewed 2026-05-19.