🏠 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 🎤 Mock Interview Community 🏗️ Company Wise 👥 About Us Start Learning Free →

LWC Zero to Hero Module 1 — Modern JavaScript Essentials for LWC

LWC Zero to Hero - Module 1: Modern JavaScript Essentials for LWC | SF Interview Pro
⚡ LWC Zero to Hero — Module 1 of 15

Modern JavaScript Essentials
Every LWC Developer Needs

Before writing a single Lightning Web Component, master the ES6+ JavaScript features that LWC is built on. Every concept here is tied directly to a real LWC pattern you'll use constantly.

Module 1 of 15 · Phase 1: JavaScript Foundations
🎯 What You'll Master in This Module
LWC components are written in modern JavaScript (ES6/ES2015 and beyond). If you've only worked with older JS (var, function declarations everywhere, callback hell), LWC code will feel confusing. This module fixes that — every concept comes with a "Why this matters for LWC" callout and a practical mini-implementation.
let/const and block scoping (why LWC never uses var)
Arrow functions and the "this" keyword problem
Template literals for dynamic strings
Destructuring (objects, arrays, function parameters)
Spread/rest operators for immutable data updates
ES6 Classes (the foundation of every LWC component)
ES Modules (import/export — how LWC files connect)
Concept 1 of 7
let, const & Block Scoping
JavaScript has three ways to declare variables: var, let, and const. LWC code uses ONLY let and const — never var. Here's why: var is function-scoped and gets "hoisted," which causes confusing bugs inside loops and conditionals. let and const are block-scoped — they only exist inside the { } where they're declared, matching how you'd expect variables to behave.
⚡ Why This Matters for LWC
Every LWC JavaScript file is a module (more on this in Concept 7) — modules automatically run in "strict mode," and modern Salesforce tooling (ESLint with the LWC plugin) will actually throw warnings/errors if you use var. Using const by default also signals intent — "this value should never be reassigned" — which makes LWC component state much easier to reason about.
❌ OLD WAY (var) — Don't do this in LWC
// var is function-scoped, not block-scoped
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3  (NOT 0, 1, 2 as you'd expect!)
// Because var "i" is shared across all iterations
✅ LWC WAY (let/const)
// let is block-scoped — each loop iteration gets its own "i"
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 0, 1, 2  — correct!

// const for values that never get reassigned
const MAX_RECORDS = 50;
const apiEndpoint = '/services/apexrest/orders';
🛠️ Practical Mini-Implementation
In an LWC JavaScript class, you'll write code like this constantly:

const MAX_ITEMS_TO_SHOW = 5; for a constant that configures component behavior, and
let filteredRecords = []; for a variable you'll reassign after filtering data.

Rule of thumb: Start with const. Only switch to let if you get an error saying you can't reassign it. You'll rarely if ever need var.
⚠️ Common Gotcha
const means the binding can't be reassigned — not that the value is fully immutable. const arr = [1,2,3]; arr.push(4); is perfectly legal — you're mutating the array's contents, not reassigning arr itself. Only arr = [5,6,7] (full reassignment) would throw an error.
Concept 2 of 7
Arrow Functions & the "this" Keyword
Arrow functions (=>) are a shorter syntax for writing functions — but their REAL superpower is how they handle this. Regular functions create their own this context. Arrow functions inherit this from their surrounding scope. This single difference is why LWC code is full of arrow functions.
⚡ Why This Matters for LWC
Inside an LWC class, this refers to your component instance — giving you access to this.recordId, this.template, this.dispatchEvent(), etc. If you use a regular function for an event handler or a callback (like inside .then() or setTimeout), this gets "lost" and no longer points to your component — causing the dreaded "this.someProperty is undefined" error. Arrow functions fix this automatically.
❌ THE "this" PROBLEM
export default class OrderList extends LightningElement {
  orders = [];

  loadOrders() {
    fetchOrders().then(function(result) {
      // 'this' here is undefined or wrong context!
      this.orders = result; // 💥 ERROR
    });
  }
}
✅ FIXED WITH ARROW FUNCTION
export default class OrderList extends LightningElement {
  orders = [];

  loadOrders() {
    fetchOrders().then((result) => {
      // 'this' correctly refers to the component!
      this.orders = result; // ✅ Works!
    });
  }

  // Arrow function syntax variations:
  double = (n) => n * 2;                    // implicit return
  greet = (name) => { return `Hi ${name}`; }; // explicit return
  logClick = () => console.log(this.orders.length);
🛠️ Practical Mini-Implementation
Every time you write a click handler that needs to update component state:

handleSaveClick = () => { this.isSaving = true; this.saveRecord(); };

Notice the class field arrow function syntax (handleSaveClick = () => {...}) — this is the most common pattern for event handlers in LWC because it guarantees this is always bound correctly, even when passed as a callback to setTimeout, promises, or child components.
⚠️ Common Gotcha
Arrow functions do NOT have their own this, arguments, or work with new. Never use an arrow function as an LWC lifecycle hook override target if you're trying to call super.connectedCallback() patterns — lifecycle hooks should be regular method syntax: connectedCallback() {...}, not connectedCallback = () => {...}.
Concept 3 of 7
Template Literals
Template literals use backticks (`) instead of quotes, and let you embed expressions directly inside strings using ${ }. No more clunky 'Hello ' + name + ', you have ' + count + ' items' string concatenation.
⚡ Why This Matters for LWC
You'll use template literals constantly for building dynamic CSS class strings, SOQL-like query strings for Apex calls, error messages, and toast notifications. They also support multi-line strings without \n concatenation — useful for readable error messages.
❌ OLD WAY — String Concatenation
const message = 'Hello ' + firstName + ', you have ' + orderCount + ' pending orders.';

// Dynamic CSS classes — painful
const cssClass = 'order-card' + (isUrgent ? ' order-card--urgent' : '');
✅ LWC WAY — Template Literals
const message = `Hello ${firstName}, you have ${orderCount} pending orders.`;

// Dynamic CSS classes — clean
get cardClass() {
  return `order-card ${this.isUrgent ? 'order-card--urgent' : ''}`;
}

// Building Apex parameters dynamically
const filter = `Status = '${this.selectedStatus}' AND Region = '${this.region}'`;
🛠️ Practical Mini-Implementation
In your HTML template, you can't write ${} syntax directly — but in your JS, getters that return template-literal strings are perfect for computed CSS classes:

get rowClass() { return `slds-table_row ${this.isSelected ? 'slds-is-selected' : ''}`; }

Then in HTML: <tr class={rowClass}> — the getter result is automatically used.
⚠️ Common Gotcha
Template literals are JavaScript-only — you cannot use backtick template literal syntax directly inside your LWC HTML template (the .html file). For dynamic text in HTML, use a getter in JS that returns the computed string, then reference {thatGetter} in the template.
Concept 4 of 7
Destructuring (Objects, Arrays, Parameters)
Destructuring lets you "unpack" values from objects and arrays into individual variables in one line. Instead of const name = record.Name; const email = record.Email; you write const { Name, Email } = record;.
⚡ Why This Matters for LWC
Apex and @wire responses return deeply nested objects ({ data, error } from wire adapters, { fields: { Name: { value }}} from getRecord). Destructuring is THE tool for cleanly extracting exactly what you need from these structures without writing response.data.fields.Name.value chains everywhere.
✅ OBJECT DESTRUCTURING — @wire responses
// Without destructuring
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
wiredAccount({ data, error }) {  // 👈 destructured params!
  if (data) {
    this.accountName = data.fields.Name.value;
  } else if (error) {
    this.errorMessage = error.body.message;
  }
}

// Destructuring with renaming + defaults
const { Name: accountName, Industry = 'Unknown' } = account;
// accountName = account.Name, Industry defaults to 'Unknown' if missing
✅ ARRAY DESTRUCTURING
// Grab first and rest of an array
const [firstOrder, ...remainingOrders] = this.orders;

// Swap variables (classic use case)
[a, b] = [b, a];

// Destructuring in function parameters — very common!
function renderOrderCard({ Id, Name, Amount, Status }) {
  return `${Name}: $${Amount} (${Status})`;
}
🛠️ Practical Mini-Implementation
When calling an imperative Apex method that returns an object with multiple values:

const { records, totalCount, hasMore } = await getAccountsWithStats();

This single line replaces three separate lines of result.records, result.totalCount, result.hasMore — and makes it immediately clear what shape of data you're expecting from the Apex method.
⚠️ Common Gotcha
Destructuring a property that doesn't exist gives you undefined, NOT an error — const { nonExistentField } = record; silently returns undefined. This can hide bugs where you mistyped a field API name. Always verify field names match exactly (including __c for custom fields) when destructuring Salesforce record data.
Concept 5 of 7
Spread & Rest Operators (...)
The ... syntax has two opposite uses. Spread "expands" an array/object into individual elements (used when creating NEW arrays/objects). Rest "collects" multiple elements INTO an array (used in function parameters and destructuring). Same symbol, opposite direction.
⚡ Why This Matters for LWC
LWC's reactivity system detects changes when you reassign a tracked property to a NEW array/object reference — not when you mutate the existing one in place. Spread syntax is THE way to create these new references for immutable updates, which is essential for triggering re-renders correctly.
❌ MUTATION — LWC may not detect this change!
// Directly pushing to a tracked array — unreliable re-render
this.orders.push(newOrder);  // 🚫 same array reference

// Directly mutating an object property
this.account.Name = 'New Name';  // 🚫 same object reference
✅ SPREAD — Creates new reference, triggers re-render
// Adding to an array immutably
this.orders = [...this.orders, newOrder];

// Removing an item immutably (combine with filter)
this.orders = this.orders.filter(o => o.Id !== orderIdToRemove);

// Updating one field in an object immutably
this.account = { ...this.account, Name: 'New Name' };

// Merging default config with user overrides
const config = { ...defaultConfig, ...userConfig };

// Copying an array before sorting (avoid mutating original)
const sortedOrders = [...this.orders].sort((a,b) => b.Amount - a.Amount);
✅ REST — Collecting arguments/properties
// Rest in function parameters — collect remaining args into array
function logAll(first, ...others) {
  console.log('First:', first, 'Rest:', others);
}

// Rest in destructuring — separate one field from "everything else"
const { Id, ...otherFields } = accountRecord;
// Id = accountRecord.Id, otherFields = all remaining fields
🛠️ Practical Mini-Implementation
A classic LWC pattern — updating a single field in an editable record form before saving:

handleFieldChange(event) {
  const { name, value } = event.target;
  this.formData = { ...this.formData, [name]: value };
}


Notice the combination: destructuring to get name/value from the event, spread to copy existing form data, and a computed property name ([name]) to dynamically set the field that changed. This single pattern handles ANY input field in a form with one handler.
⚠️ Common Gotcha
Spread creates a shallow copy. { ...this.account } copies top-level properties, but if account.Address is itself an object, the COPY's Address still points to the SAME nested object as the original. Mutating copy.Address.City would affect the original too. For deeply nested data, you need to spread at each level, or use structuredClone().
Concept 6 of 7
ES6 Classes — The Foundation of Every LWC
Every single Lightning Web Component is, at its core, an ES6 class that extends LightningElement. Understanding class syntax — fields, methods, getters, constructors, and inheritance — is non-negotiable for LWC.
⚡ Why This Matters for LWC
Literally everything you build is export default class MyComponent extends LightningElement { ... }. Class fields become reactive properties, class methods become handlers, getters become computed template values, and extends is how your component inherits all the built-in LWC lifecycle and DOM APIs.
✅ ANATOMY OF AN LWC CLASS
import { LightningElement, api, track } from 'lwc';

export default class OrderCard extends LightningElement {

  // Class fields = reactive properties
  @api orderId;              // public property (parent can set this)
  @track isExpanded = false;  // private reactive property
  errorMessage = '';       // also reactive (primitives auto-track)

  // Getter = computed property, used in template as {statusLabel}
  get statusLabel() {
    return this.isExpanded ? 'Collapse' : 'Expand';
  }

  // Lifecycle hook (inherited from LightningElement)
  connectedCallback() {
    console.log(`Component connected: ${this.orderId}`);
  }

  // Regular method = event handler (arrow function field syntax)
  handleToggle = () => {
    this.isExpanded = !this.isExpanded;
  };
}
✅ INHERITANCE — Sharing logic across components
// baseListComponent.js — shared base class
export default class BaseListComponent extends LightningElement {
  isLoading = false;

  async fetchWithLoading(apexMethod) {
    this.isLoading = true;
    try {
      return await apexMethod();
    } finally {
      this.isLoading = false;
    }
  }
}

// orderList.js — extends the shared base
import BaseListComponent from 'c/baseListComponent';

export default class OrderList extends BaseListComponent {
  // inherits isLoading and fetchWithLoading() automatically!
}
🛠️ Practical Mini-Implementation
A getter-based pattern you'll write in nearly every component — conditionally showing UI based on data state:

get hasOrders() { return this.orders && this.orders.length > 0; }
get isEmpty() { return !this.hasOrders && !this.isLoading; }

Then in HTML: <template lwc:if={hasOrders}>...</template> and <template lwc:if={isEmpty}>No orders found</template>. Getters keep your template logic-free and your conditions named/readable.
⚠️ Common Gotcha
Getters in LWC are recalculated every time the component re-renders — they're not cached. Avoid expensive operations (like sorting large arrays or API calls) inside getters, since they may run far more often than you expect. For expensive computations, calculate once and store the result in a tracked property instead.
Concept 7 of 7
ES Modules — import / export
ES Modules let you split code across files and explicitly share (export) or use (import) functionality between them. Every LWC .js file is a module — this is literally what the "M" stands for conceptually (Lightning Web Component = built on Web Component standards + ES Modules).
⚡ Why This Matters for LWC
You'll write import statements at the top of EVERY LWC file — importing LightningElement from 'lwc', Apex methods from '@salesforce/apex/...', custom labels, static resources, and your own utility modules. Understanding named vs default exports prevents the constant "import is undefined" errors beginners hit.
✅ NAMED EXPORTS — multiple exports per file, import with { }
// file: utils/formatters.js
export function formatCurrency(amount) {
  return new Intl.NumberFormat('en-IN', {
    style: 'currency', currency: 'INR'
  }).format(amount);
}

export const MAX_PAGE_SIZE = 50;

// file: orderList.js — import with curly braces, names must match
import { formatCurrency, MAX_PAGE_SIZE } from 'c/formatters';
✅ DEFAULT EXPORTS — one per file, import with any name
// Every LWC component file has exactly ONE default export
export default class OrderList extends LightningElement {
  // ...
}

// Salesforce-specific module imports you'll use constantly:
import { LightningElement, api, track, wire } from 'lwc';
import getOrders from '@salesforce/apex/OrderController.getOrders';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';
import ORDER_STATUS_FIELD from '@salesforce/schema/Order__c.Status__c';
import companyLogo from '@salesforce/resourceUrl/companyLogo';
🛠️ Practical Mini-Implementation
Creating a reusable constants file shared across multiple components in your "XYZ Company" project:

// orderConstants.js
export const ORDER_STATUSES = ['Draft', 'Submitted', 'Approved', 'Fulfilled'];
export const STATUS_COLORS = { Draft: 'grey', Approved: 'green' };

// any component
import { ORDER_STATUSES, STATUS_COLORS } from 'c/orderConstants';

Now every component that needs these values imports from ONE source of truth — change it once, updates everywhere.
⚠️ Common Gotcha
Mixing up named and default import syntax is the #1 beginner error. import getOrders from '@salesforce/apex/...' (NO curly braces) because Apex method imports are default exports. But import { LightningElement } from 'lwc' (WITH curly braces) because lwc exports many named things. If you get "X is not a function" or "X is undefined," check whether you need { } or not first.
💬 Module 1 Interview Questions (5)
Q1Why does LWC enforce using let/const instead of var? What error would you see if you used var?
LWC files are ES Modules, which automatically run in strict mode — and the platform's ESLint configuration (eslint-config-lwc) specifically flags var usage as a linting error during build. The deeper reason is that var is function-scoped and hoisted, causing bugs in loops/closures (each iteration shares one variable instead of getting its own). let/const are block-scoped, matching expected behavior. Using var in an LWC component will typically cause an ESLint error during deployment: "Unexpected var, use let or const instead."
"LWC enforces let/const via ESLint because var's function-scoping and hoisting cause closure bugs — block-scoping with let/const gives predictable behavior, especially in loops and async callbacks."
Q2Explain why arrow functions are preferred over regular functions for event handlers in LWC. Give an example of a bug caused by NOT using one.
Arrow functions don't have their own this binding — they inherit this from the enclosing scope (the class instance). Regular functions create their own this, which becomes undefined (in strict mode/modules) when called as a callback. Bug example: fetchData().then(function(result) { this.data = result; }) — inside the regular function, this is undefined, so this.data = result throws "Cannot set property 'data' of undefined." Using .then((result) => { this.data = result; }) fixes it because the arrow function's this still refers to the component instance.
"Arrow functions inherit 'this' from the surrounding class, so this.property assignments inside callbacks (.then, setTimeout, event handlers) correctly reference the component — regular functions lose that binding and cause 'this is undefined' errors."
Q3You have this code: this.items.push(newItem); and the UI doesn't update. What's wrong and how do you fix it?
The issue is mutation vs reassignment. push() mutates the existing array IN PLACE — the array's reference (memory address) doesn't change. LWC's reactivity system for tracked properties detects changes by comparing references for arrays/objects — if the reference is identical, it may not trigger a re-render reliably. The fix is to create a NEW array using spread syntax: this.items = [...this.items, newItem]; — this assigns a brand-new array reference to this.items, which LWC's reactivity system reliably detects as a change.
"push() mutates the array in place without changing its reference, so LWC's reactivity may not detect it — always reassign with spread syntax: this.items = [...this.items, newItem] to create a new reference."
Q4What's the difference between a named export and a default export? Give one Salesforce-specific example of each.
A file can have only ONE default export but MULTIPLE named exports. Default exports are imported WITHOUT curly braces and can be given any name by the importer: import getOrders from '@salesforce/apex/OrderController.getOrders' — Apex method imports are always default exports. Named exports are imported WITH curly braces and the name must match exactly (unless using "as" to rename): import { LightningElement, api, track } from 'lwc' — the lwc module exports many things by name, so you import only what you need.
"Default exports (one per file, imported without braces) are used for Apex methods and component classes; named exports (multiple per file, imported with braces) are used for the lwc module's LightningElement, api, track, and wire."
Q5How would you use destructuring to safely handle a wire adapter response that might have either data or an error?
The standard wire adapter callback signature destructures the response parameter directly: wiredAccount({ data, error }) { if (data) { /* handle success */ } else if (error) { /* handle error */ } }. This destructures the incoming object into two variables in the function signature itself. You can go further — if you only care about a specific field on success, you can destructure deeper (though typically in a second step due to nesting): const { fields } = data; const accountName = fields.Name.value; — or combine with optional chaining for safety: const accountName = data?.fields?.Name?.value;.
"Destructure the wire response signature directly as ({ data, error }), check which one is populated, then destructure further into the nested fields object to extract specific field values cleanly."
📝 Module 1 Recap — What You Now Know
let/const replace var — block-scoped, no hoisting bugs, ESLint-enforced in LWC
Arrow functions preserve "this" — essential for event handlers and promise callbacks
Template literals (backticks + ${}) replace string concatenation for dynamic strings and CSS classes
Destructuring cleanly extracts values from wire responses, Apex results, and event objects
Spread/rest (...) creates new array/object references — critical for LWC reactivity with immutable updates
ES6 Classes are the foundation — fields = reactive properties, getters = computed values, methods = handlers
ES Modules (import/export) — named vs default exports determine whether you use { } when importing
🎯 Before Moving to Module 2...
Try this exercise: Open any existing LWC component you've written (or look at a Trailhead sample). Find at least 3 places where these concepts are used — an arrow function, a destructuring pattern, and a spread operator. If you can identify WHY each one is used (not just that it's there), you're ready for Module 2: LWC Component Anatomy & Lifecycle Hooks, where we start building real components from scratch.
☕ Enjoyed this article?
SF Interview Pro is 100% free and maintained by a Salesforce professional. No ads, no paywalls, and no signup required. If this guide helped you prepare for an interview, earn a certification, or grow your Salesforce career, consider buying me a coffee! ☕💜
UPI QR Code to support sfinterviewpro
rajnishrk264@ybl
Scan with GPay, PhonePe, Paytm, or BHIM