LWC Interview Questions – Real Scenario Practice

Prepare for LWC interviews with practical, scenario-based questions that test both component concepts and problem-solving skills.

1. Scenario: Parent-Child Communication with Data Updates

Question: You have a parent component that displays a list of accounts. When a user clicks an account, a child component should display its details. If the user edits the details in the child, the parent list should update. How would you implement this?

Answer: Use @api property for parent-to-child and event dispatch for child-to-parent:


// Parent Component
import { LightningElement, track, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
    @track selectedAccount;
    @track accounts;

    @wire(getAccounts)
    wiredAccounts({ error, data }) {
        if (data) {
            this.accounts = data;
        }
    }

    handleSelectAccount(event) {
        const accountId = event.currentTarget.dataset.id;
        this.selectedAccount = this.accounts.find(acc => acc.Id === accountId);
    }

    handleAccountUpdate(event) {
        const updatedAccount = event.detail;
        const index = this.accounts.findIndex(acc => acc.Id === updatedAccount.Id);
        if (index > -1) {
            this.accounts[index] = { ...updatedAccount };
            this.accounts = [...this.accounts];  // Trigger reactivity
        }
        this.selectedAccount = null;
    }
}
  

// Child Component
import { LightningElement, api } from 'lwc';
import updateAccount from '@salesforce/apex/AccountController.updateAccount';

export default class AccountDetails extends LightningElement {
    @api account;
    @api
    accountName;

    handleSave() {
        updateAccount({ accountId: this.account.Id, accountName: this.accountName })
            .then(() => {
                this.dispatchEvent(new CustomEvent('accountupdated', {
                    detail: { ...this.account, Name: this.accountName }
                }));
            });
    }
}
  

Key Points

2. Scenario: Handling Conditional Rendering Based on User Role

Question: Your component needs to show different UI elements based on the user's role. Admins see all data, managers see their team's data, and regular users see only their own data. How do you implement this?

Answer: Use sharing rules and conditional rendering:


import { LightningElement, track, wire } from 'lwc';
import { isGuest } from 'lightning/platformShowToastEvent';
import getUserInfo from '@salesforce/apex/UserController.getUserInfo';
import getRecordsForUser from '@salesforce/apex/RecordController.getRecordsForUser';

export default class RoleBasedView extends LightningElement {
    @track userRole;
    @track records = [];
    @track isAdmin = false;
    @track isManager = false;

    @wire(getUserInfo)
    wiredUserInfo({ data, error }) {
        if (data) {
            this.userRole = data.UserRole.Name;
            this.isAdmin = this.userRole.includes('Admin');
            this.isManager = this.userRole.includes('Manager');
        }
    }

    @wire(getRecordsForUser, { userRole: '$userRole' })
    wiredRecords({ data, error }) {
        if (data) {
            this.records = data;
        }
    }

    get hasAdminAccess() {
        return this.isAdmin;
    }

    get hasManagerAccess() {
        return this.isManager;
    }
}
  

<template>
    <lightning-card title="Records">
        <!-- Admin view -->
        <template if:true={hasAdminAccess}>
            <lightning-datatable
                key-field="Id"
                data={records}
                columns={allColumns}
            ></lightning-datatable>
        </template>

        <!-- Manager view -->
        <template if:true={hasManagerAccess}>
            <lightning-datatable
                key-field="Id"
                data={records}
                columns={teamColumns}
            ></lightning-datatable>
        </template>

        <!-- Regular user view -->
        <template if:false={hasAdminAccess}>
            <template if:false={hasManagerAccess}>
                <lightning-card title="My Records">
                    <lightning-datatable
                        key-field="Id"
                        data={records}
                        columns={userColumns}
                    ></lightning-datatable>
                </lightning-card>
            </template>
        </template>
    </lightning-card>
</template>
  

Key Points

3. Scenario: Handling Form Validation and Error Messages

Question: You need to build a form that validates email, phone, and required fields before submission. If validation fails, show inline error messages. How do you implement this?

Answer: Use validation handlers and showToastEvent:


import { LightningElement, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import saveContact from '@salesforce/apex/ContactController.saveContact';

export default class ContactForm extends LightningElement {
    @track contact = {
        firstName: '',
        lastName: '',
        email: '',
        phone: ''
    };
    @track errors = {};
    @track isLoading = false;

    handleInputChange(event) {
        const field = event.target.name;
        this.contact[field] = event.target.value;
        this.errors[field] = this.validateField(field, this.contact[field]);
    }

    validateField(field, value) {
        const errors = {};
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        const phoneRegex = /^\d{10}$/;

        switch (field) {
            case 'firstName':
            case 'lastName':
                if (!value || value.trim() === '') {
                    return field + ' is required';
                }
                break;
            case 'email':
                if (!value || !emailRegex.test(value)) {
                    return 'Enter a valid email address';
                }
                break;
            case 'phone':
                if (value && !phoneRegex.test(value.replace(/\D/g, ''))) {
                    return 'Phone must be 10 digits';
                }
                break;
        }
        return '';
    }

    handleSubmit() {
        // Validate all fields
        let hasErrors = false;
        for (const field in this.contact) {
            const error = this.validateField(field, this.contact[field]);
            if (error) {
                this.errors[field] = error;
                hasErrors = true;
            }
        }

        if (hasErrors) {
            this.showToast('Validation Error', 'Please fix all errors', 'error');
            return;
        }

        this.isLoading = true;
        saveContact({ contact: this.contact })
            .then(() => {
                this.showToast('Success', 'Contact saved', 'success');
                this.resetForm();
            })
            .catch(error => {
                this.showToast('Error', error.body.message, 'error');
            })
            .finally(() => {
                this.isLoading = false;
            });
    }

    resetForm() {
        this.contact = {
            firstName: '',
            lastName: '',
            email: '',
            phone: ''
        };
        this.errors = {};
    }

    showToast(title, message, variant) {
        this.dispatchEvent(
            new ShowToastEvent({
                title,
                message,
                variant,
                mode: 'dismissable'
            })
        );
    }
}
  

<template>
    <lightning-card title="Contact Form">
        <div class="slds-p-around_medium">
            <lightning-input
                type="text"
                name="firstName"
                label="First Name"
                value={contact.firstName}
                onchange={handleInputChange}
                message-when-bad-input={errors.firstName}
            ></lightning-input>

            <lightning-input
                type="text"
                name="lastName"
                label="Last Name"
                value={contact.lastName}
                onchange={handleInputChange}
                message-when-bad-input={errors.lastName}
            ></lightning-input>

            <lightning-input
                type="email"
                name="email"
                label="Email"
                value={contact.email}
                onchange={handleInputChange}
                message-when-bad-input={errors.email}
            ></lightning-input>

            <lightning-input
                type="tel"
                name="phone"
                label="Phone"
                value={contact.phone}
                onchange={handleInputChange}
                message-when-bad-input={errors.phone}
            ></lightning-input>

            <lightning-button
                variant="brand"
                label={isLoading ? 'Saving...' : 'Submit'}
                onclick={handleSubmit}
                disabled={isLoading}
            ></lightning-button>
        </div>
    </lightning-card>
</template>
  

Key Points

4. Scenario: Fetching Data with Wire vs Imperative

Question: You have a search box that should fetch accounts based on user input. Should you use @wire or imperative Apex? Why?

Answer: Use imperative for user-triggered searches:


// ❌ WASTEFUL: @wire with parameter
@wire(searchAccounts, { searchTerm: '$searchTerm' })
wiredAccounts;

// ✅ BETTER: Imperative call on user input
import { LightningElement, track } from 'lwc';
import searchAccounts from '@salesforce/apex/AccountController.searchAccounts';

export default class AccountSearch extends LightningElement {
    @track searchTerm = '';
    @track results = [];
    @track isLoading = false;

    handleSearch(event) {
        this.searchTerm = event.target.value;

        if (this.searchTerm.length < 2) {
            this.results = [];
            return;
        }

        this.isLoading = true;
        searchAccounts({ searchTerm: this.searchTerm })
            .then(data => {
                this.results = data;
            })
            .catch(error => {
                console.error('Search failed:', error);
            })
            .finally(() => {
                this.isLoading = false;
            });
    }
}
  

Key Points

5. Scenario: Performance Optimization with Lazy Loading

Question: Your component displays a list of 1000+ records with pagination. The page loads slowly. How do you optimize?

Answer: Use pagination with lazy loading:


import { LightningElement, track } from 'lwc';
import getRecords from '@salesforce/apex/RecordController.getRecords';

export default class LazyLoadList extends LightningElement {
    @track records = [];
    @track pageNumber = 1;
    @track pageSize = 20;
    @track totalRecords = 0;
    @track isLoading = false;

    connectedCallback() {
        this.loadRecords();
    }

    loadRecords() {
        this.isLoading = true;
        getRecords({
            pageNumber: this.pageNumber,
            pageSize: this.pageSize
        })
            .then(data => {
                this.records = data.records;
                this.totalRecords = data.totalCount;
            })
            .finally(() => {
                this.isLoading = false;
            });
    }

    handleNextPage() {
        this.pageNumber++;
        this.loadRecords();
    }

    handlePreviousPage() {
        if (this.pageNumber > 1) {
            this.pageNumber--;
            this.loadRecords();
        }
    }

    get hasNextPage() {
        return (this.pageNumber * this.pageSize) < this.totalRecords;
    }

    get hasPreviousPage() {
        return this.pageNumber > 1;
    }
}
  

Key Points

6. Scenario: @Wire for Reactive Data vs Imperative for User Actions

Question: You need to show data from Apex on load and refresh automatically when the record changes. Do you use @wire or imperative call, and why?

Answer: Use @wire for reactive auto-refresh, and imperative for user-triggered actions:


import { LightningElement, api, wire, track } from 'lwc';
import getAccountDetails from '@salesforce/apex/AccountController.getAccountDetails';
import updateAccount from '@salesforce/apex/AccountController.updateAccount';

export default class AccountViewer extends LightningElement {
    @api recordId;
    @track accountData;
    @track isEditing = false;

    // ✅ Use @wire for reactive, auto-refreshing data
    @wire(getAccountDetails, { recordId: '$recordId' })
    wiredAccount({ error, data }) {
        if (data) {
            this.accountData = data;
        } else if (error) {
            console.error('Error loading account:', error);
        }
    }

    // ❌ Don't use @wire for user-triggered actions
    // @wire(updateAccount, { data: '$editedData' })
    // wiredUpdate;

    // ✅ Instead, use imperative for manual actions
    handleEdit() {
        this.isEditing = true;
    }

    handleSave() {
        updateAccount({ accountData: this.accountData })
            .then(() => {
                // After save, @wire automatically refreshes the data
                // due to recordId change or use refreshApex
                this.isEditing = false;
            })
            .catch(error => {
                console.error('Save failed:', error);
            });
    }
}
  

Key Points

7. Scenario: Bidirectional Parent-Child Communication

Question: Parent component needs to pass data to child and get response back. How do you handle communication both ways?

Answer: Use @api properties and custom events:


// Parent Component
import { LightningElement, track } from 'lwc';

export default class ParentAccount extends LightningElement {
    @track selectedAccount = null;
    @track accounts = [
        { Id: '1', Name: 'Acme Corp' },
        { Id: '2', Name: 'Tech Inc' }
    ];

    handleSelectAccount(event) {
        const accountId = event.currentTarget.dataset.id;
        this.selectedAccount = this.accounts.find(acc => acc.Id === accountId);
    }

    handleChildSave(event) {
        const updatedAccount = event.detail;
        const index = this.accounts.findIndex(acc => acc.Id === updatedAccount.Id);
        if (index > -1) {
            this.accounts[index] = updatedAccount;
        }
    }

    handleChildCancel() {
        this.selectedAccount = null;
    }
}
  

<!-- Parent Template -->
<template>
    <lightning-card title="Accounts">
        <div class="slds-p-around_medium">
            <template for:each={accounts} for:item="acc">
                <lightning-button 
                    key={acc.Id}
                    label={acc.Name}
                    onclick={handleSelectAccount}
                    data-id={acc.Id}
                ></lightning-button>
            </template>
        </div>
    </lightning-card>

    <template if:true={selectedAccount}>
        <c-account-editor 
            account={selectedAccount}
            onsave={handleChildSave}
            oncancel={handleChildCancel}
        ></c-account-editor>
    </template>
</template>
  

// Child Component
import { LightningElement, api } from 'lwc';
import updateAccount from '@salesforce/apex/AccountController.updateAccount';

export default class AccountEditor extends LightningElement {
    @api account;
    accountName;

    connectedCallback() {
        this.accountName = this.account.Name;
    }

    handleInputChange(event) {
        this.accountName = event.target.value;
    }

    handleSave() {
        updateAccount({ 
            accountId: this.account.Id, 
            accountName: this.accountName 
        })
            .then(() => {
                const updatedAccount = { ...this.account, Name: this.accountName };
                this.dispatchEvent(new CustomEvent('save', {
                    detail: updatedAccount
                }));
            });
    }

    handleCancel() {
        this.dispatchEvent(new CustomEvent('cancel'));
    }
}
  

Key Points

8. Scenario: Performance Optimization with Caching

Question: A component is slow because of repeated server calls. How do you optimize performance?

Answer: Use caching and memoization:


import { LightningElement, track } from 'lwc';
import getRecords from '@salesforce/apex/RecordController.getRecords';

export default class OptimizedRecordList extends LightningElement {
    @track records = [];
    @track isLoading = false;
    recordCache = new Map();  // Cache records by filter

    handleFilterChange(event) {
        const filter = event.target.value;

        // Check cache first
        if (this.recordCache.has(filter)) {
            this.records = this.recordCache.get(filter);
            return;
        }

        // Fetch only if not in cache
        this.isLoading = true;
        getRecords({ filter })
            .then(data => {
                this.records = data;
                this.recordCache.set(filter, data);  // Store in cache
            })
            .finally(() => {
                this.isLoading = false;
            });
    }

    // Add debounce for search to prevent multiple calls
    handleSearch(event) {
        clearTimeout(this.searchTimeout);
        this.searchTimeout = setTimeout(() => {
            this.handleFilterChange(event);
        }, 300);
    }
}
  

Key Points

9. Scenario: Loading Spinner and Error Handling

Question: You need to show a spinner while loading and handle API errors gracefully. How would you design it?

Answer: Use loading state and error messages:


import { LightningElement, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountListWithSpinner extends LightningElement {
    @track accounts = [];
    @track isLoading = false;
    @track error = '';

    connectedCallback() {
        this.loadAccounts();
    }

    loadAccounts() {
        this.isLoading = true;
        this.error = '';

        getAccounts()
            .then(data => {
                this.accounts = data;
                this.error = '';
            })
            .catch(error => {
                this.error = this.getErrorMessage(error);
                this.showErrorToast(this.error);
            })
            .finally(() => {
                this.isLoading = false;
            });
    }

    getErrorMessage(error) {
        if (error.body && error.body.message) {
            return error.body.message;
        }
        return 'An unexpected error occurred';
    }

    showErrorToast(message) {
        this.dispatchEvent(new ShowToastEvent({
            title: 'Error',
            message,
            variant: 'error'
        }));
    }

    handleRetry() {
        this.loadAccounts();
    }
}
  

<!-- Template with Spinner and Error Handling -->
<template>
    <lightning-card title="Accounts">
        <!-- Spinner while loading -->
        <template if:true={isLoading}>
            <div class="slds-p-around_medium">
                <lightning-spinner alternative-text="Loading..."></lightning-spinner>
            </div>
        </template>

        <!-- Error message with retry button -->
        <template if:true={error}>
            <div class="slds-p-around_medium slds-box slds-theme_alert-texture slds-theme_error">
                <p>{error}</p>
                <lightning-button 
                    label="Retry"
                    variant="brand"
                    onclick={handleRetry}
                ></lightning-button>
            </div>
        </template>

        <!-- Data table when loaded -->
        <template if:true={accounts}>
            <template if:false={isLoading}>
                <lightning-datatable
                    key-field="Id"
                    data={accounts}
                    columns={columns}
                ></lightning-datatable>
            </template>
        </template>
    </lightning-card>
</template>
  

Key Points

10. Scenario: Fixing Reactivity Issues

Question: Data is not updating in UI after Apex call. What could be wrong and how do you fix reactivity issues?

Answer: LWC uses shallow equality checks. Create new references for objects and arrays:


import { LightningElement, track } from 'lwc';
import updateAccount from '@salesforce/apex/AccountController.updateAccount';

export default class ReactivityExample extends LightningElement {
    @track account = { Id: '123', Name: 'Acme' };
    @track accountList = [{ Id: '1', Name: 'Client A' }];

    // ❌ WRONG: Direct mutation doesn't trigger reactivity
    handleUpdateWrong() {
        this.account.Name = 'Updated Name';  // No reactivity!
        this.accountList[0].Name = 'New Name';  // No reactivity!
    }

    // ✅ CORRECT: Create new references
    handleUpdateCorrect() {
        // For objects: spread operator
        this.account = { ...this.account, Name: 'Updated Name' };

        // For arrays: create new array
        this.accountList = [
            { ...this.accountList[0], Name: 'New Name' },
            ...this.accountList.slice(1)
        ];
    }

    // ✅ BETTER: After Apex call, refresh data
    async handleSaveAndRefresh() {
        try {
            await updateAccount({ accountData: this.account });
            // Refresh by creating new instance
            this.account = { ...this.account };
        } catch (error) {
            console.error('Save failed:', error);
        }
    }

    // ✅ FOR NESTED OBJECTS
    handleUpdateNested() {
        const updatedAccount = {
            ...this.account,
            details: {
                ...this.account.details,
                phone: '555-1234'
            }
        };
        this.account = updatedAccount;
    }
}
  

Key Points

11. Scenario: Building Reusable Components

Question: You need to build a reusable component used across multiple pages. How do you design it?

Answer: Use @api properties to make components flexible:


// Reusable DataTable Component
import { LightningElement, api, track } from 'lwc';

export default class ReusableDataTable extends LightningElement {
    @api title = 'Data';
    @api columnConfig = [];  // Accept column config from parent
    @api recordData = [];    // Accept data from parent
    @api pageSize = 10;
    @api allowEdit = false;
    @api allowDelete = false;

    @track currentPage = 1;

    connectedCallback() {
        // Validate inputs
        if (!this.columnConfig || this.columnConfig.length === 0) {
            console.warn('DataTable: columnConfig is required');
        }
    }

    get paginatedData() {
        const start = (this.currentPage - 1) * this.pageSize;
        const end = start + this.pageSize;
        return this.recordData.slice(start, end);
    }

    handleEdit(event) {
        const recordId = event.detail;
        this.dispatchEvent(new CustomEvent('edit', { detail: recordId }));
    }

    handleDelete(event) {
        const recordId = event.detail;
        this.dispatchEvent(new CustomEvent('delete', { detail: recordId }));
    }
}
  

<!-- Reusable DataTable Template -->
<template>
    <lightning-card title={title}>
        <lightning-datatable
            key-field="Id"
            data={paginatedData}
            columns={columnConfig}
            hide-checkbox-column
        ></lightning-datatable>
    </lightning-card>
</template>
  

// Parent: Using Reusable Component
import { LightningElement, track } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountDashboard extends LightningElement {
    @track accounts = [];
    columnConfig = [
        { label: 'Name', fieldName: 'Name', type: 'text' },
        { label: 'Industry', fieldName: 'Industry', type: 'text' },
        { label: 'Phone', fieldName: 'Phone', type: 'phone' }
    ];

    connectedCallback() {
        getAccounts().then(data => {
            this.accounts = data;
        });
    }

    handleEdit(event) {
        const recordId = event.detail;
        console.log('Editing:', recordId);
    }
}
  

<!-- Parent Template Using Reusable Component -->
<template>
    <c-reusable-data-table
        title="All Accounts"
        record-data={accounts}
        column-config={columnConfig}
        page-size="20"
        allow-edit="true"
        onedit={handleEdit}
    ></c-reusable-data-table>
</template>
  

Key Points

12. Scenario: Using Key Directive for List Rendering

Question: A list renders incorrectly after update. How do you use key in template for rerendering?

Answer: Use key attribute in for:each loops:


import { LightningElement, track } from 'lwc';

export default class TodoList extends LightningElement {
    @track todos = [
        { id: '1', title: 'Task 1', completed: false },
        { id: '2', title: 'Task 2', completed: false }
    ];

    handleAddTodo() {
        const newTodo = {
            id: Date.now().toString(),
            title: 'New Task',
            completed: false
        };
        this.todos = [...this.todos, newTodo];
    }

    handleToggleTodo(event) {
        const todoId = event.currentTarget.dataset.id;
        const index = this.todos.findIndex(t => t.id === todoId);
        if (index > -1) {
            // Create new array to trigger reactivity
            this.todos = [
                ...this.todos.slice(0, index),
                { ...this.todos[index], completed: !this.todos[index].completed },
                ...this.todos.slice(index + 1)
            ];
        }
    }

    handleDeleteTodo(event) {
        const todoId = event.currentTarget.dataset.id;
        this.todos = this.todos.filter(t => t.id !== todoId);
    }
}
  

<!-- Template with Proper Key -->
<template>
    <lightning-card title="Todos">
        <!-- ❌ DON'T: Use index as key -->
        <!-- <template for:each={todos} for:item="todo" for:index="index">
            <li key={index}>...</li>
        </template> -->

        <!-- ✅ DO: Use unique identifier -->
        <ul>
            <template for:each={todos} for:item="todo">
                <li key={todo.id} data-id={todo.id}>
                    <span class={completedClass}>{todo.title}</span>
                    <lightning-button
                        variant="bare"
                        icon-name="utility:check"
                        onclick={handleToggleTodo}
                        data-id={todo.id}
                    ></lightning-button>
                    <lightning-button
                        variant="bare"
                        icon-name="utility:delete"
                        onclick={handleDeleteTodo}
                        data-id={todo.id}
                    ></lightning-button>
                </li>
            </template>
        </ul>
    </lightning-card>
</template>
  

Key Points

13. Scenario: Permission-Based UI Rendering

Question: You must restrict UI based on user permissions. How do you handle security in LWC?

Answer: Check permissions before rendering sensitive UI:


import { LightningElement, track, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import Id from '@salesforce/user/Id';
import getUserPermissions from '@salesforce/apex/UserController.getUserPermissions';

const FIELDS = ['Account.Id', 'Account.Name'];

export default class PermissionBasedView extends LightningElement {
    userId = Id;
    @track account;
    @track userPermissions = {};

    @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
    wiredRecord({ data, error }) {
        if (data) {
            this.account = data;
        }
    }

    @wire(getUserPermissions)
    wiredPermissions({ data, error }) {
        if (data) {
            this.userPermissions = {
                canRead: data.canReadAccounts,
                canEdit: data.canEditAccounts,
                canDelete: data.canDeleteAccounts,
                isAdmin: data.isAdmin
            };
        }
    }

    get canEditAccount() {
        return this.userPermissions.canEdit;
    }

    get canDeleteAccount() {
        return this.userPermissions.canDelete;
    }

    handleEdit() {
        if (!this.canEditAccount) {
            alert('You do not have permission to edit this record');
            return;
        }
        // Proceed with edit
    }
}
  

<!-- Template with Permission Checks -->
<template>
    <lightning-card title="Account Details">
        <template if:true={account}>
            <p>{account.fields.Name.value}</p>
        </template>

        <!-- Only show edit button if user has permission -->
        <template if:true={canEditAccount}>
            <lightning-button
                label="Edit"
                variant="brand"
                onclick={handleEdit}
            ></lightning-button>
        </template>

        <!-- Only show delete button if user is admin -->
        <template if:true={userPermissions.isAdmin}>
            <lightning-button
                label="Delete"
                variant="destructive"
                onclick={handleDelete}
            ></lightning-button>
        </template>
    </lightning-card>
</template>
  

Key Points

14. Scenario: File Upload and Processing

Question: Need to upload a file and process it in Apex. What approach will you take?

Answer: Use lightning-file-upload and handle the file in Apex:


import { LightningElement, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import uploadFile from '@salesforce/apex/FileController.uploadFile';

export default class FileUploadExample extends LightningElement {
    @track uploadedFiles = [];

    handleFileUpload(event) {
        const files = event.detail.files;
        
        if (files.length === 0) return;

        const file = files[0];
        const reader = new FileReader();

        reader.onload = () => {
            const fileContent = reader.result;
            const base64 = this.getBase64(fileContent);

            // Call Apex to process file
            uploadFile({
                fileName: file.name,
                fileContent: base64,
                fileType: file.type
            })
                .then(result => {
                    this.uploadedFiles = [
                        ...this.uploadedFiles,
                        { name: file.name, recordId: result }
                    ];
                    this.showToast('Success', 'File uploaded', 'success');
                })
                .catch(error => {
                    this.showToast('Error', error.body.message, 'error');
                });
        };

        reader.readAsDataURL(file);
    }

    getBase64(fileContent) {
        return fileContent.split(',')[1];  // Remove data:image/png;base64, prefix
    }

    showToast(title, message, variant) {
        this.dispatchEvent(new ShowToastEvent({
            title,
            message,
            variant
        }));
    }
}
  

<!-- File Upload Template -->
<template>
    <lightning-card title="Upload File">
        <lightning-file-upload
            label="Select File"
            accept=".csv,.pdf,.xlsx"
            record-id="account-record"
            onuploadfinished={handleFileUpload}
        ></lightning-file-upload>

        <template if:true={uploadedFiles}>
            <p>Uploaded Files:</p>
            <ul>
                <template for:each={uploadedFiles} for:item="file">
                    <li key={file.recordId}>{file.name}</li>
                </template>
            </ul>
        </template>
    </lightning-card>
</template>
  

// Apex: Process File
public with sharing class FileController {
    @AuraEnabled
    public static String uploadFile(String fileName, String fileContent, String fileType) {
        ContentVersion cv = new ContentVersion();
        cv.Title = fileName;
        cv.VersionData = EncodingUtil.base64Decode(fileContent);
        cv.IsMajorVersion = true;
        insert cv;

        ContentDocumentLink cdl = new ContentDocumentLink();
        cdl.ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id][0].ContentDocumentId;
        cdl.LinkedEntityId = UserInfo.getUserId();  // Or your record Id
        cdl.ShareType = 'V';
        insert cdl;

        return cv.Id;
    }
}
  

Key Points

15. Scenario: Sharing Data Across Multiple Components

Question: Multiple components need same data. Do you call Apex multiple times or use LDS / shared state?

Answer: Use Lightning Data Service or a shared state pattern:


// Approach 1: Use Lightning Data Service (LDS)
import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

const FIELDS = ['Account.Id', 'Account.Name', 'Account.Phone'];

export default class AccountViewerLDS extends LightningElement {
    @api recordId;

    // LDS automatically caches data and shares across components
    @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
    account;
}
  

// Approach 2: Use Shared Data Service (Redux-like pattern)
// dataService.js
let sharedData = {};

export function setSharedData(key, data) {
    sharedData[key] = data;
}

export function getSharedData(key) {
    return sharedData[key];
}

// Component 1: Loads data
import { setSharedData } from 'c/dataService';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountLoader extends LightningElement {
    connectedCallback() {
        getAccounts().then(data => {
            setSharedData('accounts', data);  // Share data
        });
    }
}

// Component 2: Uses shared data
import { getSharedData } from 'c/dataService';

export default class AccountConsumer extends LightningElement {
    get accounts() {
        return getSharedData('accounts');
    }
}
  

// Approach 3: Parent provides data to children via @api
<template>
    <c-account-list accounts={sharedAccounts}></c-account-list>
    <c-account-summary accounts={sharedAccounts}></c-account-summary>
</template>
  

Key Points

Interview Tips

Question: A record should be private, but certain users get access dynamically via logic. How do you implement Apex sharing?

Answer: Create sharing records programmatically:


// Apex: Share Opportunity with specific user if condition is met
public class OpportunityShareService {
    
    public static void shareWithApprover(List<Opportunity> opportunities) {
        List<OpportunityShare> shares = new List<OpportunityShare>();
        
        for (Opportunity opp : opportunities) {
            // Only share if amount > $100k and approver exists
            if (opp.Amount > 100000 && opp.ApproverIds__c != null) {
                List<Id> approverIds = opp.ApproverIds__c.split(';');
                
                for (Id approverId : approverIds) {
                    OpportunityShare share = new OpportunityShare();
                    share.OpportunityId = opp.Id;
                    share.UserOrGroupId = approverId;
                    share.OpportunityAccessLevel = 'Edit';  // Read or Edit
                    shares.add(share);
                }
            }
        }
        
        if (!shares.isEmpty()) {
            insert shares;
        }
    }
    
    // Remove access when condition no longer met
    public static void removeApproverShare(List<Opportunity> opportunities) {
        List<Id> oppIds = new List<Id>(new Map<Id, Opportunity>(opportunities).keySet());
        
        List<OpportunityShare> sharesToDelete = [
            SELECT Id 
            FROM OpportunityShare 
            WHERE OpportunityId IN :oppIds 
            AND RowCause = 'Manual'
        ];
        
        if (!sharesToDelete.isEmpty()) {
            delete sharesToDelete;
        }
    }
}

// Trigger
trigger OpportunitySharingTrigger on Opportunity (after insert, after update) {
    if (Trigger.isInsert) {
        OpportunityShareService.shareWithApprover(Trigger.new);
    } else if (Trigger.isUpdate) {
        OpportunityShareService.shareWithApprover(Trigger.new);
        OpportunityShareService.removeApproverShare(Trigger.new);
    }
}
  

Key Points:

4. Profile vs Permission Set vs Sharing

Question: Difference between Profile vs Permission Set vs Sharing in real scenario?

Answer: Three different layers of access control:

Layer What It Controls Scope Example
Profile Object access, field access, system permissions User-wide Sales Profile can't access Finance Object
Permission Set Extends profile permissions (add, never remove) User-assigned Add "Manage Quotes" to Sales Rep temporarily
Sharing Record-level access control Per-record Sales Rep A can see Opp X but not Opp Y

Real Scenario:


Sales Profile (base permissions)
├─ Can access Account, Opportunity objects
├─ Cannot access Finance object
└─ Cannot see phone field

+ Permission Set (temporary)
├─ Add "Approve Quotes" ability
└─ Keep all other restrictions

+ Sharing (record control)
├─ Sally can see Account 123
├─ Sally cannot see Account 456
└─ Both accounts visible to Manager due to role hierarchy
  

5. Field-Level Security vs Object-Level vs Record-Level

Question: How do you handle field-level security vs object-level vs record-level access?

Answer: Three layers of restriction:

Level Controls Set By Example
Object-Level Can user access object at all? Profile / Permission Set Sales user cannot access Finance object
Field-Level Can user see/edit specific fields? Profile / Permission Set Sales user can see Amount but not Cost__c
Record-Level Can user access specific records? OWD / Sharing Rules / Apex Sharing Sales user can see their own Oppty, not competitor's

Access Check Order (top to bottom):


1. Can user access the Object? (OWD)
2. Can user access the Field? (FLS in Profile/PermSet)
3. Can user see this Record? (Sharing Rules / Apex Sharing)
4. Can user Edit this Field? (FLS for Edit + Record Access)

If ANY layer says NO, access is DENIED.
  

Example:


Sales user:
✅ Can access Opportunity object (OWD = Private)
✅ Can see Amount field (FLS allows read)
❌ Cannot see this specific Opp (not owner, not shared, manager)
❌ Result: User cannot see record

---

Manager:
✅ Can access Opportunity object
✅ Can see Amount field
✅ Can see Opp because below Sales Rep in hierarchy
✅ Can edit Amount (FLS allows edit)
✅ Result: Manager can view and edit
  

6. User Can See Record But Not Edit

Question: A user can see a record but not edit it. What could be the reasons?

Answer: Check in this order:

  1. Field-Level Security (FLS) – Field hidden or read-only in Profile
    • Check: Setup → Users → Profiles → Profile Name → Field Permissions
    • Fix: Grant Read/Write on field
  2. Record-Level Access – User has Read-only sharing, not Edit
    • Check: Record Sharing Details → see user's access level
    • Fix: Change OpportunityShare.AccessLevel = 'Edit'
  3. Object-Level Permissions – Profile can't edit object type
    • Check: Profile → Object Permissions → Edit access
    • Fix: Enable Edit on object
  4. Org-Level Restrictions – Org-wide defaults prevent edit
    • Check: Setup → Sharing Settings → OWD for object
    • Fix: Change to Read/Write or controlled by sharing rules
  5. Record Locked/Approval – Record in approved stage
    • Check: Approval process locked record
    • Fix: Unlock record or use bypass sharing

7. Public Groups vs Roles

Question: When would you use Public Groups vs Roles?

Answer: Different purposes and behaviors:

Aspect Roles Public Groups
Hierarchy Hierarchical (tree structure) Flat (just membership)
Inheritance Parent sees child records No inheritance
Use Case Org structure, manager visibility Cross-functional teams, skill-based groups
Example CEO → Manager → Sales Rep "Finance Approvers", "East Coast Team", "Data Analysts"
Sharing Rules Can use "Based on Role" Can't (must use "Based on Role" for hierarchy)

Scenario: Share records to "Finance Team" (includes people from Sales, Ops, Legal)


Solution: Use Public Group "Finance Team" with members from different roles.
Sharing Rule: IF Approver_Group__c = 'Finance', share with PublicGroup.FinanceTeam
  

8. Community User Related Records Access

Question: A community user needs limited access to related records. How do you design external sharing model?

Answer: Use Account-based sharing:


// Community User sees:
// ✅ Their own User record
// ✅ Their Account (customer's account)
// ✅ Contacts related to that Account (via relationship)
// ✅ Cases they created or are related to Account
// ❌ Sensitive opportunity details

// Setup:
Setup → Sharing Settings:
- Account OWD: Controlled by Parent
- Contact OWD: Controlled by Parent (Account)
- Case OWD: Public Read/Write or Controlled by Parent
- Opportunity OWD: Private (not visible to community)

// Apex: Ensure portal user only sees their Account data
public with sharing class ContractController {
    @AuraEnabled(cacheable=true)
    public static List<Contract__c> getMyContracts() {
        // with sharing automatically filters to user's accessible records
        return [
            SELECT Id, Name, Amount__c, Status__c
            FROM Contract__c
            WHERE Account__c IN (SELECT AccountId FROM User WHERE Id = :UserInfo.getUserId())
        ];
    }
}
  

9. OWD Public Read/Write But Still Want Restrictions

Question: What happens when OWD is Public Read/Write but you still want restrictions?

Answer: Use Anti-Sharing Rules or custom logic:


// If OWD = Public Read/Write, everyone sees all records.
// To restrict: Use anti-sharing or custom validation.

// Option 1: Validation Rule (prevents edit, not view)
IF(
    AND(
        $Profile.Name != 'System Administrator',
        Sensitivity__c = 'Confidential'
    ),
    TRUE
)
// Blocks non-admins from editing Confidential records (but they can still see)

// Option 2: Custom Logic in Apex (deny read/write)
public with sharing class AccountController {
    @AuraEnabled
    public static Account getAccount(Id accountId) {
        Account acc = [SELECT Id, Name, SensitiveData__c FROM Account WHERE Id = :accountId];
        
        // Deny access if sensitive and not admin
        if (acc.Sensitivity__c = 'Confidential' && !isAdmin()) {
            throw new AuraHandledException('Access Denied');
        }
        return acc;
    }
}

// Option 3: Remove OWD = Public Read/Write, use Sharing Rules instead
// ✅ Better approach: Set OWD to Private/Read Only, grant access via Sharing Rules
  

10. Troubleshooting "User Can't See Record"

Question: How do you troubleshoot "user can't see record" issue step by step?

Answer: Use this checklist:


STEP 1: Check Org-Wide Defaults (OWD)
├─ Go to Setup → Sharing Settings → [Object]
├─ Read: "The account level is set to Private"
├─ Q: Can user see ANY records of this type? 
│   └─ If NO → Likely OWD or Field-Level Security issue

STEP 2: Check Record Sharing
├─ Open the record → sharing icon (bottom right)
├─ Q: Is user listed with 'Read' or 'Edit' access?
│   └─ If NO → Add via sharing rule or manual share
│   └─ If YES → Drill down to field level

STEP 3: Check Profile/Permission Set
├─ Setup → Users → User Details → [User]
├─ Check Profile → Object Permissions
├─ Q: Does profile have READ access to object?
│   └─ If NO → Add to profile or permission set
│   └─ If YES → Check field access next

STEP 4: Check Field-Level Security
├─ Profile → Field Permissions → [Object]
├─ Q: Is the FIELD (or key fields) readable?
│   └─ If NO → Enable read access on field
│   └─ If YES → Check record ownership/hierarchy

STEP 5: Check Role Hierarchy
├─ Is user owner of record? YES → can see
├─ Is user above owner in hierarchy? YES → can see
├─ Is record shared? Check sharing rules/manual share
├─ Is sharing rule matching? YES → can see

STEP 6: Check Apex Sharing
├─ Query ShareWith__c or custom sharing logic
├─ Is record dynamically shared? Check trigger logs

STEP 7: Last Resort – Check System
├─ Clear browser cache (Ctrl+Shift+Del)
├─ Login as different user and test
├─ Check org debug logs for AuraEnabled exceptions
└─ Run: SOSL search to see if record appears
  

11. Sales Reps See Only Their Records, Managers See Team Data

Question: Sales reps should see only their records, managers should see team data. How do you design access?

Answer: Combine OWD + Role Hierarchy:


// Org Structure:
VP Sales
├─ Regional Manager - West
│  ├─ Sales Rep 1
│  └─ Sales Rep 2
└─ Regional Manager - East
   └─ Sales Rep 3

// Configuration:
1. OWD for Opportunity = PRIVATE
2. Sales Rep owns their own Opportunities
3. Role Hierarchy grants manager visibility

// Results:
Sales Rep 1:
- Sees their own Oppty (as owner)
- Cannot see Oppty owned by Sales Rep 2
- Cannot see Regional Manager's Oppty

Regional Manager - West:
- Sees all Oppty owned by Sales Rep 1 & 2 (below in hierarchy)
- Sees their own Oppty (as owner)
- Cannot see Regional Manager - East's Oppty

VP Sales:
- Sees all Oppty (everyone below in hierarchy)

// Adding Apex Sharing for exceptions:
If an Oppty needs review by Finance team:

public class OpportunityShareService {
    public static void shareWithFinanceForReview(List<Opportunity> opportunities) {
        List<OpportunityShare> shares = new List<OpportunityShare>();
        
        for (Opportunity opp : opportunities) {
            if (opp.FinanceReview__c == true) {
                OpportunityShare share = new OpportunityShare();
                share.OpportunityId = opp.Id;
                share.UserOrGroupId = PublicGroupId.Finance;  // Public Group ID
                share.OpportunityAccessLevel = 'Read';
                shares.add(share);
            }
        }
        insert shares;
    }
}
  

12. Finance Read-Only Access Across Regions

Question: Finance needs read-only access to all Opportunities across regions. What approach will you use?

Answer: Use Sharing Rule with Public Group:


// Step 1: Create Public Group for Finance
Setup → Users → Public Groups → New
Name: Finance Team
Add members: All Finance users, Finance manager

// Step 2: Create Sharing Rule
Setup → Sharing Settings → Opportunity → New
Rule Name: Finance View All Oppty
Type: Opportunity
Share With: Public Group (Finance Team)
Access Level: Read

Criteria:
- All Opportunities (no filters)
OR
- RecordType = "Enterprise"
OR
- StageName IN ('Proposal', 'Negotiation')

// Result:
- Every Finance user can READ all Oppty
- Finance users cannot EDIT Oppty
- Finance users cannot delete Oppty
- Access applies globally across regions

// Apex: Verify access
public with sharing class FinanceReporting {
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getAllOpportunitiesForReporting() {
        // with sharing respects OWD and sharing rules
        return [
            SELECT Id, Name, Amount, StageName, OwnerId, Account.Name
            FROM Opportunity
            ORDER BY Amount DESC
        ];
    }
}
  

13. Temporary Access to a Record

Question: You need to give temporary access of a record to a user. How would you handle it?

Answer: Use scheduled Apex or workflow to auto-revoke access:


import { LightningElement, api, track } from 'lwc';
import grantTemporaryAccess from '@salesforce/apex/SharingService.grantTemporaryAccess';

export default class TemporaryAccessComponent extends LightningElement {
    @api recordId;
    @track accessDays = 7;
    @track selectedUser;

    async handleGrantAccess() {
        try {
            await grantTemporaryAccess({
                recordId: this.recordId,
                userId: this.selectedUser,
                days: this.accessDays
            });
            alert('Access granted until ' + new Date(Date.now() + this.accessDays * 24 * 60 * 60 * 1000));
        } catch (error) {
            alert('Error: ' + error.body.message);
        }
    }
}

// Apex: Grant and auto-revoke access
public class SharingService {
    
    @AuraEnabled
    public static void grantTemporaryAccess(Id recordId, Id userId, Integer days) {
        // Insert share record
        OpportunityShare share = new OpportunityShare();
        share.OpportunityId = recordId;
        share.UserOrGroupId = userId;
        share.OpportunityAccessLevel = 'Edit';
        share.RowCause = 'Manual';
        insert share;
        
        // Create scheduled action to remove access
        scheduleAccessRevoke(recordId, userId, days);
    }
    
    private static void scheduleAccessRevoke(Id recordId, Id userId, Integer days) {
        RevokeAccessJob job = new RevokeAccessJob(recordId, userId);
        String cronExpression = getCronExpression(days);
        System.schedule('Revoke_' + recordId, cronExpression, job);
    }
}

// Scheduled Job: Revoke access
public class RevokeAccessJob implements Schedulable {
    private Id recordId;
    private Id userId;
    
    public RevokeAccessJob(Id recordId, Id userId) {
        this.recordId = recordId;
        this.userId = userId;
    }
    
    public void execute(SchedulableContext ctx) {
        List<OpportunityShare> shares = [
            SELECT Id 
            FROM OpportunityShare 
            WHERE OpportunityId = :recordId 
            AND UserOrGroupId = :userId 
            AND RowCause = 'Manual'
        ];
        
        if (!shares.isEmpty()) {
            delete shares;
        }
    }
}
  

14. Dynamic Sharing Based on Field Value

Question: A record should be shared dynamically based on a field value. What solution will you use?

Answer: Use Apex trigger with field-based logic:


// Scenario: Share Opportunity with Account team when Status = "Reviewing"

trigger OpportunityFieldBasedSharing on Opportunity (after insert, after update) {
    if (Trigger.isAfter) {
        OpportunityFieldSharingService.shareBasedOnStatus(Trigger.new, Trigger.oldMap);
    }
}

public class OpportunityFieldSharingService {
    
    public static void shareBasedOnStatus(List<Opportunity> newOppty, Map<Id, Opportunity> oldMap) {
        List<OpportunityShare> sharesToAdd = new List<OpportunityShare>();
        List<Id> opptyToUnshare = new List<Id>();
        
        for (Opportunity opp : newOppty) {
            Opportunity oldOpp = oldMap.get(opp.Id);
            String newStatus = opp.StageName;
            String oldStatus = oldOpp.StageName;
            
            // If changed to "Reviewing", share with Account team
            if (newStatus == 'Reviewing' && oldStatus != 'Reviewing') {
                // Get account team members
                List<AccountTeamMember> teamMembers = [
                    SELECT UserId FROM AccountTeamMember 
                    WHERE AccountId = :opp.AccountId
                ];
                
                for (AccountTeamMember member : teamMembers) {
                    OpportunityShare share = new OpportunityShare();
                    share.OpportunityId = opp.Id;
                    share.UserOrGroupId = member.UserId;
                    share.OpportunityAccessLevel = 'Read';
                    sharesToAdd.add(share);
                }
            }
            
            // If changed from "Reviewing" to other status, revoke access
            if (oldStatus == 'Reviewing' && newStatus != 'Reviewing') {
                opptyToUnshare.add(opp.Id);
            }
        }
        
        // Insert new shares
        if (!sharesToAdd.isEmpty()) {
            insert sharesToAdd;
        }
        
        // Remove old shares
        if (!opptyToUnshare.isEmpty()) {
            List<OpportunityShare> sharesToDelete = [
                SELECT Id FROM OpportunityShare 
                WHERE OpportunityId IN :opptyToUnshare 
                AND RowCause = 'Manual'
            ];
            delete sharesToDelete;
        }
    }
}
  

15. Users Can See Records But Cannot Edit Certain Fields

Question: Users can see records but cannot edit certain fields. How do you control this?

Answer: Combine Field-Level Security + Validation Rules + Lightning Components:


// Approach 1: Field-Level Security (simplest)
Setup → Profiles → [Profile] → Field Permissions
- Amount field: Read enabled, Edit disabled
- Result: User sees Amount but input field is read-only

// Approach 2: Validation Rule (more control)
IF(
    AND(
        NOT($Profile.Name = 'System Administrator'),
        CHANGED(Renewal_Date__c),
        Renewal_Date__c > TODAY()
    ),
    TRUE
)
Error Message: "Non-admins cannot edit future renewal dates"

// Approach 3: Lightning Component (most control)
import { LightningElement, api, track, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import getFieldEditability from '@salesforce/apex/FieldSecurityService.getFieldEditability';

export default class RestrictedFieldForm extends LightningElement {
    @api recordId;
    @track fieldPermissions = {};

    @wire(getFieldEditability, { recordId: '$recordId' })
    wiredPermissions({ data, error }) {
        if (data) {
            this.fieldPermissions = data;
        }
    }

    get canEditAmount() {
        return this.fieldPermissions.Amount?.editable;
    }

    handleSave() {
        // Validate field edits before sending to Apex
        if (!this.canEditAmount) {
            alert('You cannot edit Amount field');
            return;
        }
        // Proceed with save
    }
}

// Apex: Check FLS before allowing edit
public with sharing class FieldSecurityService {
    
    @AuraEnabled
    public static Map<String, FieldPermission> getFieldEditability(Id recordId) {
        Map<String, FieldPermission> fieldAccess = new Map<String, FieldPermission>();
        
        Schema.DescribeSobjectResult describe = recordId.getSObjectType().getDescribe();
        Map<String, Schema.SObjectField> fields = describe.fields.getMap();
        
        for (String fieldName : new List<String>{ 'Amount', 'RenewalDate', 'Description' }) {
            Schema.SObjectField field = fields.get(fieldName.toLowerCase());
            Schema.DescribeFieldResult fieldDesc = field.getDescribe();
            
            FieldPermission perm = new FieldPermission();
            perm.readable = fieldDesc.isAccessible();
            perm.editable = fieldDesc.isUpdateable();
            
            fieldAccess.put(fieldName, perm);
        }
        
        return fieldAccess;
    }
    
    public class FieldPermission {
        @AuraEnabled public Boolean readable { get; set; }
        @AuraEnabled public Boolean editable { get; set; }
    }
}
  

16. External (Community) Users See Only Their Related Records

Question: External (community) users should only see their own related records. How do you design it?

Answer: Use Account-based sharing + Apex with sharing:


// Community user setup:
1. Portal User linked to Account (customer account)
2. OWD settings for Customer Portal:
   - Account: Controlled by Parent (Account)
   - Contact: Controlled by Parent (Account)
   - Case: Public Read/Write (so they can see all cases)
   - Opportunity: Private

3. Portal User sees:
   ✅ Their Account (the company they work for)
   ✅ Contacts on their Account
   ✅ Cases on their Account
   ❌ Other companies' data

// Apex: Enforce access in code
public with sharing class CommunityDataService {
    
    @AuraEnabled(cacheable=true)
    public static List<Contact> getAccountContacts() {
        // with sharing automatically filters to user's account
        User portalUser = [SELECT AccountId FROM User WHERE Id = :UserInfo.getUserId()];
        
        return [
            SELECT Id, Name, Email, Phone 
            FROM Contact 
            WHERE AccountId = :portalUser.AccountId
        ];
    }

    @AuraEnabled(cacheable=true)
    public static List<Case> getMyAccountCases() {
        User portalUser = [SELECT AccountId FROM User WHERE Id = :UserInfo.getUserId()];
        
        return [
            SELECT Id, CaseNumber, Subject, Status 
            FROM Case 
            WHERE AccountId = :portalUser.AccountId
        ];
    }
}

// LWC: Display community data
import { LightningElement, track, wire } from 'lwc';
import getAccountContacts from '@salesforce/apex/CommunityDataService.getAccountContacts';

export default class CommunityView extends LightningElement {
    @track contacts = [];

    @wire(getAccountContacts)
    wiredContacts({ data, error }) {
        if (data) {
            this.contacts = data;
        }
    }
}
  

17. Troubleshooting: User Cannot See Record Step by Step

Question: A user cannot see a record they should have access to. How do you troubleshoot step by step?

Answer: Use this systematic troubleshooting approach:


// Step 1: Verify user can see records at all (OWD baseline)
SELECT Id, Name FROM Opportunity LIMIT 10
// If query returns records → OWD allows some visibility
// If query returns 0 records → Check OWD (likely Private) or FLS

// Step 2: Check if user owns the record
SELECT OwnerId FROM Opportunity WHERE Id = :recordId
IF (OwnerId = UserInfo.getUserId()) {
    CONCLUSION: User is owner, should have full access
    CHECK: Profile edit permissions
}

// Step 3: Check role hierarchy
User u = [SELECT UserRoleId FROM User WHERE Id = :UserInfo.getUserId()];
UserRole r = [SELECT Id, ParentRoleId FROM UserRole WHERE Id = :u.UserRoleId];

IF (record.OwnerId's role is below current user's role hierarchy) {
    CONCLUSION: User should see record via role hierarchy
    CHECK: OWD is not blocking it (set to Private at minimum)
}

// Step 4: Check if record is shared explicitly
List<OpportunityShare> shares = [
    SELECT Id, AccessLevel 
    FROM OpportunityShare 
    WHERE OpportunityId = :recordId 
    AND (UserOrGroupId = :userId OR UserOrGroupId IN (public groups user belongs to))
];

IF (shares.size() > 0) {
    CONCLUSION: Record is explicitly shared
    IF (AccessLevel = 'Read') → User can READ but not EDIT
}

// Step 5: Check Field-Level Security
Schema.DescribeFieldResult fieldDesc = Opportunity.Amount.getDescribe();
IF (!fieldDesc.isAccessible()) {
    PROBLEM: User cannot read Amount field
    FIX: Enable field in Profile/PermSet
} ELSE IF (!fieldDesc.isUpdateable()) {
    PROBLEM: User can read but cannot edit Amount
    FIX: User has Read access to record but not edit to this field
}

// Step 6: Check for API filters
// Some record types may be hidden by validation rules or SOSL
SELECT COUNT() FROM Opportunity WHERE Id = :recordId
IF (count = 0) {
    PROBLEM: Record doesn't match user's query filters
    CHECK: Are you using with sharing clause?
    FIX: Ensure Apex uses with sharing for correct filtering
}

// Step 7: Verify record actually exists and isn't deleted
SELECT Id FROM Opportunity WHERE Id = :recordId ALL ROWS
IF (IsDeleted = true) {
    CONCLUSION: Record is in recycle bin
    FIX: Restore from recycle bin if needed
}

// Debug Query to get complete picture:
List<Opportunity> opps = [
    SELECT 
        Id, Name, OwnerId, Owner.Name, Amount,
        (SELECT Id, UserOrGroupId, OpportunityAccessLevel FROM OpportunityShares)
    FROM Opportunity 
    WHERE Id = :recordId
];
  

18. Restrict Access When OWD is Public Read/Write

Question: You need to restrict access even when OWD is Public Read/Write. What can you do?

Answer: OWD = Public Read/Write gives global access; use alternatives:


// PROBLEM: If OWD = Public Read/Write, EVERYONE can see ALL records
// SOLUTION OPTIONS:

// Option 1: Change OWD to Private or Read Only ✅ BEST
Setup → Sharing Settings → Opportunity
Change: Public Read/Write → Private
Then grant access via Sharing Rules/Apex Sharing

// Option 2: Use Validation Rules (prevents EDIT, not VIEW)
IF(
    AND(
        Confidentiality__c = 'Sensitive',
        NOT($Profile.Name = 'System Administrator')
    ),
    TRUE
)
Error: "Only admins can edit sensitive records"
// Note: Users can still SEE sensitive records, just can't edit

// Option 3: Use Apex with custom restrictions
public with sharing class OpportunitySecurity {
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getAccessibleOpportunities() {
        // Apex with sharing respects OWD and Sharing Rules
        List<Opportunity> opps = [
            SELECT Id, Name, Amount 
            FROM Opportunity
        ];
        
        // Add custom filter to deny based on field
        List<Opportunity> filtered = new List<Opportunity>();
        for (Opportunity opp : opps) {
            if (opp.Confidentiality__c != 'TopSecret' || isCurrentUserAdmin()) {
                filtered.add(opp);
            }
        }
        return filtered;
    }
}

// Option 4: Use record-level access control in LWC
// Even if record is visible via SOQL, hide in LWC based on field
import { LightningElement, track } from 'lwc';

export default class OpportunitiesList extends LightningElement {
    @track visibleOpportunities = [];

    handleDataLoaded(event) {
        const allOpps = event.detail;
        
        // Filter in UI if Confidentiality = TopSecret
        this.visibleOpportunities = allOpps.filter(
            opp => opp.Confidentiality__c !== 'TopSecret'
        );
    }
}

// RECOMMENDATION:
// Change OWD from Public Read/Write to Private
// Grant access explicitly via Sharing Rules or Apex
// This is the proper security model
  

19. Multiple Teams Need Access – Roles or Public Groups?

Question: Multiple teams need access to the same set of records. Do you use Roles or Public Groups? Why?

Answer: Depends on team structure:

Scenario Use Roles Use Public Groups
Sales team hierarchy (VP → Manager → Rep) ✅ YES – hierarchy matters ❌ No – not hierarchical
Cross-functional teams (Sales + Finance + Legal) ❌ No – not structural ✅ YES – skill-based, not org hierarchy
Finance needs all Oppty (across all regions) ❌ No – they're not in sales org ✅ YES – create "Finance Approvers" group
Multiple account teams for same account ❌ No – not structural ✅ YES – "West Region Team", "East Region Team"

Real Example:


Scenario: Three teams need access to "Enterprise Opportunities"
- Sales team (hierarchical)
- Finance review team (cross-functional)
- Legal review team (ad-hoc)

Solution:
1. Create Sharing Rule for "Sales" based on Roles
   Share with role "Sales" + subordinates

2. Create Public Group "Finance Approvers"
   Add: Finance VP, Finance Manager, Auditor role

3. Create Public Group "Legal Team"
   Add: Legal counsel, in-house attorney, paralegal

4. Create two Sharing Rules:
   Rule 1: IF Requires_Finance_Review__c = true
           Share with Public Group "Finance Approvers"
   
   Rule 2: IF Requires_Legal_Review__c = true
           Share with Public Group "Legal Team"

Result:
- Sales sees records in their role
- Finance sees only records needing finance review
- Legal sees only records needing legal review
  

20. Complex Sharing Logic – Sharing Rules or Apex?

Question: You need to share records from a custom object based on complex logic. Would you use Sharing Rules or Apex Sharing? Why?

Answer: Choose based on complexity:

Scenario Sharing Rules Apex Sharing
Share if field = "X" ✅ Simple and fast ❌ Overkill
Share if field = "X" AND role = "Y" ✅ Good (use role-based rule) ❌ Sharing rules handle this
Share based on lookup field value (Account region) ❌ Can't do cross-object logic ✅ YES – query parent record, evaluate
Share if related Contact matches criteria ❌ No cross-object logic ✅ YES – query related records
Complex: Share to users on same team as record owner ❌ Can't query team membership ✅ YES – use Apex for complex joins

Example: Complex Sharing Rule


Requirement: Share Project record with all users in same Department as owner

// Sharing Rules can't do this (no cross-object logic)
// Solution: Use Apex

trigger ProjectSharingTrigger on Project__c (after insert, after update) {
    ProjectSharingService.shareWithDepartment(Trigger.new);
}

public class ProjectSharingService {
    
    public static void shareWithDepartment(List<Project__c> projects) {
        // Get owner departments
        Set<Id> ownerIds = new Set<Id>();
        for (Project__c proj : projects) {
            ownerIds.add(proj.OwnerId);
        }
        
        // Query owners and their department
        Map<Id, String> ownerDepartments = new Map<Id, String>();
        for (User u : [SELECT Id, Department FROM User WHERE Id IN :ownerIds]) {
            ownerDepartments.put(u.Id, u.Department);
        }
        
        // Find all users in same departments
        Set<String> departments = new Set<String>(ownerDepartments.values());
        List<User> departmentUsers = [
            SELECT Id FROM User 
            WHERE Department IN :departments
        ];
        
        // Create shares
        List<Project__Share> shares = new List<Project__Share>();
        for (Project__c proj : projects) {
            String ownerDept = ownerDepartments.get(proj.OwnerId);
            
            for (User u : departmentUsers) {
                if (u.Department == ownerDept) {
                    Project__Share share = new Project__Share();
                    share.ParentId = proj.Id;
                    share.UserOrGroupId = u.Id;
                    share.AccessLevel = 'Edit';
                    share.RowCause = 'Manual';
                    shares.add(share);
                }
            }
        }
        
        insert shares;
    }
}

// DECISION RULE:
// If: Simple field-based rule → Use Sharing Rules ✅
// If: Complex, cross-object, queryable logic → Use Apex ✅
// If: Mix of both → Sharing Rules + Apex (don't duplicate logic)
  

Interview Tips