Salesforce Interview Prep

Module 2 — ANSWER SHEET (SEALED)

Companion to 02_Topic02_LWC_UI_JS.md — open ONLY after you have written your own attempt.

Protocol (file 00, M1→M2): write ≥2 hypotheses + 2 solution attempts before reading. Then compare your attempt against this sheet like an investigator compares a suspect's story to the evidence. The contrast IS the learning. Then REDO from memory, then do the drill.


INCIDENT 1 — THE INFINITE RENDER LOOP

The problem restated

One line of "harmless" state change is the assassin. Which line, why does it loop, and what is the correct structure for "do this once after first render"?

Model answer (2-min interview version)

  • The assassin: this.isLoading = false; inside renderedCallback(). renderedCallback fires after every render; assigning a tracked property there schedules a re-render → renderedCallback again → infinite loop → the browser thread never yields → "Page Unresponsive." (The spinner's if:true guaranteed a DOM change on first assignment, so the first re-render was certain.)
  • The fix — two patterns:
    1. One-time init: guard flag set BEFORE the work:
    renderedCallback() {
        if (this.chartInit) return;
        this.chartInit = true;
        this.initChart();          // DOM exists here
    }
    1. Data-driven redraws: never through renderedCallback state. Use an @api setter (or the @wire callback) that calls this.redraw() only when the chart already exists.
  • Rule to state: reactive state changes belong in event handlers, connectedCallback, and imperative flows — never in renderedCallback for control flow.

Self-grade — you "got it" if you named:

  • renderedCallback fires after every render
  • Reactive assignment → re-render → loop (the mechanism chain)
  • Guard flag pattern (set before the work)
  • connectedCallback ≠ DOM work (chart div doesn't exist yet)
  • "Redraw on data change" via @api setter / wire callback, not renderedCallback

THE REDO — model answer

export default class AccountChart extends LightningElement {
    chartInit = false;
    @api recordId;
    @wire(getRecord, { recordId: '$recordId', fields: [...] })
    account({ data, error }) {
        if (data) this.redraw(data);        // redraw path — chart exists after first render
    }

    renderedCallback() {
        if (this.chartInit) return;         // guard
        this.chartInit = true;
        this.initChart();                   // one-time DOM init
    }
}

No loop: renderedCallback never assigns reactive state; data changes flow through the wire callback, which only redraws.

RETRIEVAL DRILL — model answers

  1. Parent→child lifecycle order: Parent constructor → Parent connectedCallback → Child constructor → Child connectedCallback → Child renderedCallback → Parent renderedCallback.
  2. Why is reactive assignment in renderedCallback dangerous? → It fires after every render; the assignment marks a property dirty → schedules a re-render → the hook fires again → infinite render loop / frozen tab.
  3. Two things you may NOT do in constructor: access DOM (none exists) and call Apex/@wire logic (and any super() must be the first statement; properties aren't set yet).
  4. What is errorCallback for, and what does it NOT catch? → Catches errors thrown in descendant components (error boundary, fallback UI). It does NOT catch errors in your own code or event handlers.
  5. Is @track required for a plain object field? → No — all fields are reactive since Spring '20; shallow reactivity is default since Winter '21 (API 49). @track is only for deep observation of plain object/array internals, and it does NOT observe class instances, Date, Map, Set.

INCIDENT 2 — THE WIRE THAT NEVER FIRED

The problem restated

Wire works on record pages, dead on App/Experience pages, zero errors. Mechanism + robust fix.

Model answer (2-min interview version)

  • Mechanism: @wire(getRecord, { recordId: '$recordId', ... }) — the $ prefix makes recordId a reactive input: the wire is deferred until the input has a defined value. @api recordId is auto-populated only on record pages. On App pages, Experience Cloud pages, utility bars — no record context → recordId stays undefined → the wire never fires. Not an error: a deferred provisioning that never resolves. That's why there's no console error and no Apex call.
  • Fix: detect no-record-context in connectedCallback; fall back to the page reference (currentPageReference().state.recordId) when on an App page with a record in the URL; if still none → render an explicit empty state. Always render loading/empty/error states — never assume the wire fired.
  • Bonus (why "it's cached, refresh it" was wrong): refreshApex only works on data actually provisioned by @wire — a never-fired wire has nothing to refresh.

Self-grade — you "got it" if you named:

  • $ reactive param + undefined = deferred forever
  • recordId populated only on record pages
  • No error/no Apex call = "never provisioned," not "cached"
  • Fallback strategy (page reference / imperative / parent-provided)
  • Explicit empty-state UX

THE REDO — model answer

@api recordId;
@wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME_FIELD] })
wiredAccount;

connectedCallback() {
    if (!this.recordId && this.pageRef?.state?.recordId) {
        this.recordId = this.pageRef.state.recordId;
    }
    this.hasContext = !!this.recordId;
}

$recordId = reactive: wire waits and re-provisions when it changes. Plain recordId = static: evaluated once, never re-runs on change.

RETRIEVAL DRILL — model answers

  1. $ input undefined at evaluation? → The wire does not fire (deferred until defined). Silently.
  2. What must an Apex method have to be wireable?@AuraEnabled(cacheable=true).
  3. refreshApex — when does it work, when NOT? → Works on data provisioned by a @wire (Apex wire). Does NOT work on: cacheable methods called imperatively, never-provisioned wires, LDS adapters.
  4. 3 situations where a wire re-provisions: reactive input changed; refreshApex called; component re-rendered / page navigation.
  5. Wire vs imperative for a Save button: imperative — DML can't be cacheable, and control/loading/error belong to the button handler.

INCIDENT 3 — THE STALE SCREEN

The problem restated

One side refreshes and stays stale; the other doesn't refresh at all and stays fresh. Explain the asymmetry.

Model answer (2-min interview version)

  • Two cache worlds: the Apex @wire cache (client-side cache of cacheable=true results — invalidated only by reactive changes or refreshApex) and the LDS / UI API cache (which auto-invalidates on updateRecord/createRecord/deleteRecord).
  • The asymmetry: the right panel used LDS getRecordupdateRecord auto-invalidated the LDS cache → it refreshed itself for free. The left panel used an Apex @wire → the Apex cache is blind to imperative DML (saveRow) → stayed stale.
  • The hidden trap in the dev's refreshApex: correct instinct for the left panel, BUT (a) if the wire result is mapped into a separate display array (this.rows = mapRows(data)), re-provisioning alone doesn't rebuild the table — you must re-map; (b) if the wire never provisioned (Incident 2) or the method was called imperatively, refreshApex is a silent no-op.
  • Fix:
// Left (Apex wire): after imperative save
await refreshApex(this.wiredSearch);
this.rows = mapRows(this.wiredSearch.data);   // re-map!

// Right (LDS): updateRecord auto-invalidates; add for cross-component safety
notifyRecordUpdateAvailable([this.recordId]);

Self-grade — you "got it" if you named:

  • Two distinct caches (Apex wire cache vs LDS cache)
  • LDS auto-invalidates on its own writes
  • Apex wire cache blind to imperative DML → needs refreshApex
  • Re-map display arrays after refresh
  • refreshApex silent no-op conditions

THE REDO — model answer

Panels: search = Apex @wire (filtered multi-record — Apex territory); detail = LDS getRecord (single record, auto-fresh). Post-save: left → refreshApex(this.wiredSearch) + re-map; right → nothing (auto) + notifyRecordUpdateAvailable. Datatable bulk save: onsave → Apex Database.update(draftValues, false) → map SaveResult[] failures to the table's errors attribute.

RETRIEVAL DRILL — model answers

  1. Two cache systems + who invalidates: Apex @wire cache — refreshApex / reactive input change; LDS/UI API cache — auto on LDS writes + notifyRecordUpdateAvailable.
  2. refreshApex — 3 conditions to actually work: (1) data came from a @wire; (2) the wire has provisioned at least once; (3) you pass the wired property/function result (a Promise is returned — await it).
  3. When does LDS auto-refresh other components? → When a record they read via getRecord is written via updateRecord/createRecord/deleteRecord (cache invalidation propagates).
  4. Why search stale, detail fresh? → The detail panel's LDS cache self-invalidates on save; the search panel's Apex wire cache has no idea an imperative method changed the data — it only re-provisions on refreshApex.
  5. notifyRecordUpdateAvailable? → Manually invalidates the LDS cache for given record IDs — the belt-and-braces after LDS writes (or for changes made outside LDS).

INCIDENT 4 — THE FROZEN TABLE

The problem restated

Four defects in four lines: freeze, empty Owner column, correct paged design, and where the 50,000-row limit bites.

Model answer (2-min interview version)

  1. The freeze: lightning-datatable renders real DOM per row. Sweet spot ~1,000 rows × ~5 columns; >250 rows → stay <20 columns. 10–50K rows = browser death. "50,000 is legal" is a limit, not a design target.
  2. Empty column: datatable has no nested-field supportfieldName: 'Owner.Name' renders blank. Flatten: map Owner.NameOwnerName in Apex or JS.
  3. Paged design: Apex takes offset/limit (or nextRecordsUrl); datatable uses enable-infinite-loading + onloadmore + load-more-offset (target.isLoading toggling); never render the table until data exists (spinner gate).
  4. Where the row limit bites: 50,000 rows through a wire = multi-megabyte JSON (heap server-side, memory client-side) — plus the classic "passed in sandbox (400 rows), died in prod" replay. Bulk save: send only event.detail.draftValuesDatabase.update(rows, false) → map SaveResult[] errors to the table.

Self-grade — you "got it" if you named:

  • DOM scale / rows-rendered as the freeze cause
  • No nested-field support → flatten
  • Infinite scroll wiring (enable-infinite-loading, onloadmore, load-more-offset)
  • Payload/heap cost of shipping 50K rows
  • draftValues-only bulk save + partial-success mapping

THE REDO — model answer

@AuraEnabled(cacheable=true)
public static List<Opportunity> getPage(Integer offset, Integer limit) {
    return [SELECT Id, Name, Amount, StageName, Owner.Name FROM Opportunity
            ORDER BY CreatedDate LIMIT :limit OFFSET :offset];
}
columns = [{ label: 'Owner', fieldName: 'OwnerName', type: 'text' }];
handleLoadMore(event) {
    const target = event.target;
    target.isLoading = true;
    this.offset += PAGE_SIZE;
    this.rows = [...this.rows, ...(await getPage({ offset: this.offset, limit: PAGE_SIZE }))];
    target.isLoading = false;
}

Monitor: AsyncApexJob.TotalJobItems, AsyncApexJob.JobItemsProcessed, AsyncApexJob.Status.

RETRIEVAL DRILL — model answers

  1. Datatable perf sweet spot: ~1,000 rows × 5 columns; >250 rows → <20 columns; custom data types hurt perf.
  2. Why fieldName: 'Owner.Name' renders nothing: datatable doesn't resolve nested/dot-notation fields — the column definition must be a flat property (OwnerName), which you produce in Apex/JS.
  3. Two reasons "50,000 rows is legal" is the wrong bar: (1) DOM scale — rendering tens of thousands of rows freezes the browser; (2) payload — multi-MB JSON through a wire hits heap/serialization/memory limits and destroys load time.
  4. onsave payload: event.detail.draftValues (only changed rows) → pass those to Apex, not the whole table.
  5. 3 loading-state patterns: lightning-spinner gated by isLoading; target.isLoading = true/false for datatable infinite load; not rendering the datatable until data is present.

INCIDENT 5 — THE LOST EVENT

The problem restated

Event fires (console proves it), the immediate parent doesn't hear it. Why, what does the architect want, and the difference.

Model answer (2-min interview version)

  • Mechanism: CustomEvent defaults are bubbles: false, composed: false → the event stays inside the child's own shadow DOM; nothing outside hears it. The parent (and grandparent, in another shadow root) are deaf — the console.log only proves the dispatch, not the travel.
  • The architect's fix (standards, one dispatch):
this.dispatchEvent(new CustomEvent('productselected', {
    detail: this.product,     // data rides in detail (retargeting-safe)
    bubbles: true,            // travel up the containment tree
    composed: true            // cross shadow boundaries → public API
}));
  • The rejected alternative: manually re-dispatching through every intermediate component — the Aura-era plumbing hack; fragile and redundant.
  • When LMS instead: only for unrelated components (no shared hierarchy) — it's global pub/sub; every subscriber gets every message; harder to test; leaks if you forget unsubscribe in disconnectedCallback. Events for hierarchies, LMS for strangers.

Self-grade — you "got it" if you named:

  • Defaults bubbles:false, composed:false
  • bubbles = travel up the tree; composed = cross shadow DOM
  • One dispatch serves all ancestors
  • Data in event.detail, not event.target (retargeting)
  • LMS = unrelated components only + unsubscribe in disconnectedCallback

THE REDO — model answer

Correct dispatch (above). Parent: onproductselected={handleSelected} reading event.detail. Judgment answer: filter panel ↔ results table across page areas with no shared parent → LMS with a message channel (lightning/messageService: publish/subscribe/unsubscribe, MessageContext), subscribed in connectedCallback, unsubscribed in disconnectedCallback.

RETRIEVAL DRILL — model answers

  1. Defaults: bubbles: false, composed: false. bubbles = travels up the containment tree; composed = crosses shadow DOM boundaries.
  2. Where data travels: event.detail — because event.target is retargeted to the shadow host when read from outside the dispatching component's shadow root.
  3. LMS vs CustomEvent decision rule: CustomEvent for related components in a hierarchy; LMS only for unrelated components that share no tree relationship.
  4. LMS subscriptions: unsubscribe in disconnectedCallback (and unsubscribeAll on destroy) — otherwise memory leaks.
  5. Aura application events → LWC: replaced by LMS; LWC has no application events (and no $A, no two-way binding).

INCIDENT 6 — THE SEARCH THAT KILLED THE SERVER

The problem restated

Cacheable ≠ free. Three mechanisms turn typing into an attack. Rewrite the handler including the race condition.

Model answer (2-min interview version)

  1. No debounce: one request per keystroke (~6–7/sec). Fix: setTimeout ~300 ms after the last keystroke — one call per pause.
  2. Cacheable only caches hits: each new character = a new argument = a cache miss = a real Apex execution ("salesforce" typed = up to 9 executions).
  3. Hidden server cost: request handling + SOQL + serialization every execution — and a leading-wildcard LIKE '%...%' is a non-selective full scan (the CPU replay of Module 1 Incident 3, client-driven).
  4. Race condition: a stale response ("s") arriving after the current one ("sa") renders wrong results — guard by rendering only when the response's term still equals the current input.
handleKeyChange(event) {
    const term = event.target.value;
    clearTimeout(this.debounceTimer);
    this.debounceTimer = setTimeout(async () => {
        this.isSearching = true;
        try {
            const results = await searchContacts({ term });
            if (term === this.latestTerm) this.results = results;   // race guard
        } catch (err) {
            this.error = reduceErrors(err);
        } finally {
            this.isSearching = false;
        }
    }, 300);
}

Why "cacheable = cheap" is wrong: cacheable saves repeat reads of the same input and enables @wire sharing — it doesn't throttle, cache misses, or make LIKE '%...%' selective.

Self-grade — you "got it" if you named:

  • Debounce (~300 ms, clearTimeout/setTimeout)
  • Cache-miss-per-keystroke (new input = new execution)
  • Server-side cost of each execution (serialization + non-selective LIKE)
  • Race guard (render only latest term)
  • Wire-with-reactive-term still needs debounce

THE REDO — model answer

The imperative handler above (debounce + race guard + error handling). If it were a @wire with reactive term: debounce stays (the wire re-provisions per change — actually you'd debounce the input into a debouncedTerm and bind the wire to $debouncedTerm); the race guard moves into the wiring (the wire guarantees latest-param ordering by re-provisioning, but stale data from a previous term is still possible if you cache-transform — render from the wire's data tied to the current term). Server-side regardless: escapeSingleQuotes, LIMIT, selective fields.

RETRIEVAL DRILL — model answers

  1. Why "cacheable = cheap" is wrong (3 reasons): (1) each new input is a cache miss → real execution; (2) no throttling — it doesn't debounce; (3) server cost per execution (serialization, non-selective LIKE full scan) regardless of caching.
  2. Debounce pattern: clearTimeout(this.timer); this.timer = setTimeout(() => { ... }, 300); capturing the value in a closure.
  3. Promise.all vs allSettled: all rejects fast on the first failure (parallel independent calls); allSettled waits for every promise and reports per-promise status — use when one failure shouldn't kill the batch.
  4. What does fetch NOT reject on? → HTTP error statuses (404/500) — only network failures reject; check response.ok.
  5. NodeList → array: [...nodeList] or Array.from(nodeList) (e.g., [...this.template.querySelectorAll('lightning-input')]).

INCIDENT 7 — THE XSS IN THE "TRUSTED" TEXT

The problem restated

Three defense assumptions, all failed. Secure version + the LWS truth.

Model answer (2-min interview version)

  1. "It's just text"innerHTML reparses attacker-controlled markup; XSS needs no <script> tag (<img src=x onerror=...>, javascript: URLs, SVG/foreign content). Regex sanitization of HTML is bypassable. The comment executed in every visitor's browser and (stored XSS) during authenticated sessions too.
  2. "We use a CDN" → Lightning CSP blocks inline scripts and external CDN scripts. Legit libraries go in a Static Resource and load via loadScript(this, resourceUrl + '/lib.js') / loadStyle from lightning/platformResourceLoader (dedupes, returns Promises).
  3. "LWS blocks that"LWS distorts, it doesn't block. innerHTML, eval, Function, localStorage, document.cookie all run — inside a per-namespace sandboxed JS Realm. LWS limits blast radius (defense-in-depth); it doesn't neutralize the vulnerability. Truly blocked: Workers + a tiny set.
  • Secure version: declarative template {comment.text} (auto-escaping) or lightning-formatted-rich-text for trusted rich text; server: with sharing, WITH USER_MODE, escapeSingleQuotes, input validation; Guest User least-privilege audit; Event Monitoring.
  • Cross-namespace gotcha: objects crossing LWS namespaces are Proxy-wrapped — in-place mutation silently doesn't propagate; JSON-serialize for event.detail.

Self-grade — you "got it" if you named:

  • innerHTML + untrusted data = XSS (no <script> needed)
  • CSP blocks inline + CDN → Static Resource + loadScript
  • LWS distorts, not blocks (Realm sandbox)
  • Declarative templates auto-escape
  • Server-side: with sharing + USER_MODE + escapeSingleQuotes + least privilege

THE REDO — model answer

Template: <template><div class="c">{comment.text}</div></template> (auto-escaped). Library: import LIB from '@salesforce/resourceUrl/mylib'; + loadScript(this, LIB + '/mylib.js'). Apex: public with sharing class ... + WITH USER_MODE + escapeSingleQuotes. Audit: Guest User profile — remove legacy object access; enable Event Monitoring. "Does LWS break localStorage?" → No — it runs, but inside the sandboxed per-namespace realm (distorted, isolated from the real global). The catch: it's containment, not permission — a payload can still read "its own" storage; don't put secrets in client storage.

RETRIEVAL DRILL — model answers

  1. LWS vs Locker: LWS = per-namespace JavaScript Realm sandbox that distorts APIs; default for orgs created Winter '23+; LWC. Locker = Aura-era monkey-patched DOM facades.
  2. CSP blocks: inline scripts + external CDN scripts. Legal load: Static Resource + loadScript/loadStyle (lightning/platformResourceLoader).
  3. Why LWS doesn't "fix" XSS: the payload still executes — LWS only contains the environment (sandboxed APIs, isolated realm). The vulnerability (untrusted HTML injection) remains.
  4. Two no-<script> XSS vectors: <img src=x onerror="..."> (event handler attribute) and javascript: URLs (e.g., <a href="javascript:...">); also SVG/foreign-content vectors.
  5. Cross-namespace objects: wrapped in Proxies — in-place mutation is silently lost; workaround: serialize to JSON / structured clone when passing objects across namespaces (e.g., in event.detail).

INCIDENT 8 — THE AURA GHOST

The problem restated

Who is right about embedding, who is right about the rule — and the migration strategy end-to-end.

Model answer (2-min interview version)

  • The dev is wrong twice: (1) Aura embeds LWC; LWC cannot embed Aura. The new quoting widget ships as LWC inside the existing Aura parent (attributes in, on* event handlers out) — no helper reuse needed, LWC is a fresh ES-module class. (2) "New code = LWC" is the 2026 standard; building new Aura is debt. Legitimate Aura exceptions: surgical in-place fixes to existing components, and areas where Aura is still required (parts of Service Console, some Experience Cloud/CRM Analytics overrides).
  • Migration strategy — leaves first, host last:
    1. Convert leaf/child components to LWC (the Aura parent keeps hosting them — interop is one-way, so the tree stays shippable).
    2. Translate in-place: aura:attribute@api; cmp.get/set→direct assignment; aura:if/elselwc:if/elseif/else; aura:iterationfor:each + unique key; component events→CustomEvent; application events→LMS; {!c.action}→JS method; controller/helper→one ES6 class; renderer overrides→guarded renderedCallback.
    3. Migrate the host last (re-architect as a flexipage/LWC composition), then delete the Aura bundle.
    4. Freeze new Aura work; track migration as technical debt.
  • The concept with no LWC equivalent: Aura application events → replaced by LMS.

Self-grade — you "got it" if you named:

  • Direction: Aura embeds LWC, never the reverse
  • "New code = LWC" + the narrow exceptions
  • Leaves first, host last
  • The 7-row mapping table
  • Application events → LMS (no equivalent)

THE REDO — model answer

Mapping table: aura:attribute@api; cmp.get("v.x")/cmp.set→direct field assignment; aura:if/aura:elselwc:if/lwc:elseif/lwc:else; aura:iterationfor:each + key; component events→CustomEvent (bubbles/composed); application events→LMS; {!c.action}→JS method; controller/helper→one ES6 class; renderer→guarded renderedCallback. Direction: Aura can embed LWC; LWC cannot embed Aura. 3 phases: (1) freeze new Aura, (2) migrate leaves while Aura parents host LWC children, (3) re-architect hosts (flexipage/LWC) and delete Aura bundles.

RETRIEVAL DRILL — model answers

  1. Embedding direction: Aura embeds LWC; LWC cannot embed Aura.
  2. aura:iteration and aura:if:for:each + unique key; → lwc:if / lwc:elseif / lwc:else.
  3. Aura component events / application events: component events → CustomEvent; application events → LMS (no direct LWC equivalent).
  4. Why "migrate the host first" is wrong: the host is the only thing that can embed LWC children (interop is one-way) — migrating it first blocks all child migrations and breaks the shippable-tree strategy. Leaves-first keeps the system deployable at every step.
  5. cmp.get("v.x") → LWC: direct property access (this.x); assignment triggers reactivity — no cmp.set.

🏆 CAPSTONE — MODEL DESIGN CHECKLIST

The module's model design (part 2 of the capstone section) covers the full architecture. This sheet adds the two things you must be able to produce, not just read:

The 60-second spoken walkthrough (closed notes) — model script

"Three components: contact-search owns the state; c-contact-results and c-contact-pills are presentational and communicate via CustomEvent with bubbles and composed — the parent is the single source of truth for selection. Apex: one cacheable search method — with sharing, WITH USER_MODE, escapeSingleQuotes, LIMIT 25 — and one non-cacheable sendEmails that batches into a single SingleEmailMessage[] because sendEmail caps at 10 invocations per transaction. Search is imperative with a 300 ms debounce and a race guard — wire reactivity doesn't replace either. After the send I notifyRecordUpdateAvailable and re-run the last search to keep the list honest. Every Apex error flows through reduceErrors, the send button is disabled while in flight, and I Jest-test the debounce, the race guard, and the dedupe."

The Jest test list (model sketch)

  1. Debounce: fire handleKeyChange 5× rapidly → assert searchContacts called once (fake timers).
  2. Race guard: resolve the older request after the newer one → assert the newer result is rendered.
  3. Dedupe: click the same result twice → one pill.
  4. Pill removal: click pill → remove event → parent state updated.
  5. Errors: searchContacts rejects → reduceErrors path renders the error state, no spinner stuck.
  6. Send: button disabled while in flight; success/failure toasts fired.

INTERLEAVED PRACTICE SET — model answers

  1. Apex + LWC, one bug: missing await refreshApex(this.wiredResults) after the save (plus re-mapping the table rows). The Module-1 limit the email-sending method must respect: 10 sendEmail invocations per transaction — batch into one Messaging.SingleEmailMessage[].
  2. Choose the tool: @wire getRecord (LDS) — single record, needs no Apex, auto-refreshes, enforces sharing/FLS. The related-list count is a multi-record aggregate — that part needs an Apex @wire (or a rollup summary). Split: LDS for the record, Apex wire for the count.
  3. Live-code sketch: debounce lives in the input handler (clearTimeout/setTimeout(300)); refreshApex(this.wiredCounter) in the same handler's completion (after the debounced search resolves), so the count re-provisions from the wire cache.
  4. Whiteboard: Module 1 OOE fires-more-than-once: workflow field-update re-fire (step 11) and triggers per 200-record chunk. Module 2: connectedCallback (every DOM insertion) and renderedCallback (every render).
  5. Security double (ordered by severity): (1) fix the Apex: with sharing + WITH USER_MODE + stripInaccessible + escapeSingleQuotes (stops the record/field leak — Module 1 Incident 8); (2) fix the render: declarative template instead of innerHTML (stops the stored XSS — Module 2 Incident 7); (3) audit Guest User profile (least privilege, remove legacy access); (4) enable Event Monitoring; (5) re-test with a Guest-User-run integration test.

Quick answer-key summary (print on one card)

  • Lifecycle order: P ctor → P connected → C ctor → C connected → C rendered → P rendered.
  • renderedCallback: after EVERY render — guard one-time init, never set reactive state.
  • $x undefined = wire never fires (silent). refreshApex = wire data only.
  • LDS auto-invalidates on its writes; Apex wire needs refreshApex + re-map.
  • CustomEvent: bubbles: true, composed: true for public cross-boundary events; data in detail.
  • LMS = unrelated components; unsubscribe in disconnectedCallback; no application events in LWC.
  • Debounce ~300 ms + race guard; cacheable ≠ cheap (misses still execute).
  • Datatable: flatten nested fields; ~1,000 rows × 5 cols; infinite scroll; draftValues only.
  • LWS distorts, not blocks; CSP blocks inline/CDN → Static Resource + loadScript.
  • Aura embeds LWC (never reverse); leaves first, host last; app events → LMS.

On this page

INCIDENT 1 — THE INFINITE RENDER LOOPThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answersINCIDENT 2 — THE WIRE THAT NEVER FIREDThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answersINCIDENT 3 — THE STALE SCREENThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answersINCIDENT 4 — THE FROZEN TABLEThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answersINCIDENT 5 — THE LOST EVENTThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answersINCIDENT 6 — THE SEARCH THAT KILLED THE SERVERThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answersINCIDENT 7 — THE XSS IN THE "TRUSTED" TEXTThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answersINCIDENT 8 — THE AURA GHOSTThe problem restatedModel answer (2-min interview version)Self-grade — you "got it" if you named:THE REDO — model answerRETRIEVAL DRILL — model answers🏆 CAPSTONE — MODEL DESIGN CHECKLISTThe 60-second spoken walkthrough (closed notes) — model scriptThe Jest test list (model sketch)INTERLEAVED PRACTICE SET — model answersQuick answer-key summary (print on one card)