Performance

O(n²) DOM manipulation

const items = document.querySelectorAll('.item'); const updates = getUpdates(); // array of items to color // O(n²): nested loop — performance problem for (let i = 0; i < updates.length; i++) { items.forEach(el => { el.style.color = 'red'; // triggers reflow per item el.style.fontWeight = 'bold'; el.style.background = '#fee'; }); }
/* CSS: define the state once */ .item--highlighted { color: red; font-weight: bold; background: #fee; } // JS: single pass, no nested loop const items = document.querySelectorAll('.item'); items.forEach(el => { el.classList.add('item--highlighted'); // O(n), no reflow });
Inline style changes in a nested loop force the browser to recalculate layout on every iteration — O(n²) reflows. The fix moves all style declarations into a CSS class and uses a single classList.add() pass. The browser batches the class update into one reflow.
Accessibility

Non-interactive trigger element

<!-- Not keyboard accessible --> <div class="modal-trigger" onclick="openModal()" > Open </div> <!-- Issues: - div is not focusable by keyboard - no role conveyed to screen readers - no indication it opens a dialog -->
<!-- Keyboard accessible, screen reader friendly --> <button type="button" aria-haspopup="dialog" aria-controls="modal" onclick="openModal()" > Open </button> <!-- Fixed: - button is focusable and activatable with Enter/Space - aria-haspopup="dialog" announces what opens - aria-controls links to the modal element -->
A <div> with onclick is invisible to keyboard navigation and screen readers. Replacing it with <button> makes it focusable, activatable with Enter and Space, and understood by assistive technology. The aria-haspopup and aria-controls attributes tell screen readers exactly what the button does.
Maintainability

Copy-pasted functions

// 5 almost-identical functions function showSuccessToast(msg) { const el = document.createElement('div'); el.className = 'toast toast--success'; el.textContent = msg; document.body.append(el); } function showErrorToast(msg) { const el = document.createElement('div'); el.className = 'toast toast--error'; el.textContent = msg; document.body.append(el); } // ... showWarningToast, showInfoToast, showLoadingToast
// Single function, data-driven config const TOAST_TYPES = { success: 'toast--success', error: 'toast--error', warning: 'toast--warning', info: 'toast--info', }; function showToast(msg, type = 'info') { const el = document.createElement('div'); el.className = `toast ${TOAST_TYPES[type]}`; el.textContent = msg; document.body.append(el); } // Usage showToast('Saved!', 'success'); showToast('Failed to save', 'error');
Five near-identical functions mean any bug fix or feature change must be applied five times. The refactored version uses one function with a type parameter and a config object. Adding a new toast type is now one line in the config — not a new copy-pasted function.
Security

XSS via innerHTML

// Vulnerable: userInput can contain script tags const userInput = getInputFromURL(); // XSS: if userInput = "<img src=x onerror=alert(1)>" // the browser will execute the event handler commentEl.innerHTML = userInput; // Attacker can steal cookies, redirect users, // or make requests on their behalf.
// Option A: textContent (safest — treats input as plain text) commentEl.textContent = userInput; // Option B: DOMParser sanitization (if you need to allow some HTML) function sanitize(html) { const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('script, [onclick], [onerror]') .forEach(el => el.remove()); return doc.body.innerHTML; } commentEl.innerHTML = sanitize(userInput);
Setting innerHTML with untrusted user input allows Cross-Site Scripting (XSS). An attacker can inject event handlers or script tags that execute in the victim's browser. The safest fix is textContent, which renders the value as literal text. If you need to allow some HTML, parse and strip dangerous nodes before inserting.