JavaScript & DOM ⚡ Recently updatedUpdated on Jun 30, 2026 — this content may still be evolving.
JavaScript DOM: Project-Style Questions
15 scenario questions interviewers ask about real DOM work. Practice answering with structure: approach → code outline → tradeoffs.
1. Build a Todo App Without a Framework
Question: How would you build a todo app with vanilla JS?
Answer outline:
- HTML: form (input + button),
<ul>for list - JS: on submit,
preventDefault, create<li>withcreateElement, append to list - Event delegation on
<ul>for delete/complete clicks - Optional:
localStoragefor persistence
Tradeoffs: Simple and no build step; doesn’t scale to complex state. Fine for learning and small apps.
2. Event Delegation on Dynamic Lists
Question: You add list items dynamically. Why attach one listener to the parent instead of each item?
Answer: Items may not exist at page load. One listener on parent handles current and future children via bubbling. Fewer listeners = better memory and simpler cleanup.
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.matches('.delete')) {
e.target.closest('li').remove();
}
});
3. textContent vs innerHTML
Question: When would you use each?
Answer:
textContent: plain text, safe from XSS, fasterinnerHTML: when you need HTML markup (trusted content only)- Never use
innerHTMLwith unsanitized user input
4. Fetch and Render a List from API
Question: Load users from an API and display them. Walk through your approach.
Answer:
async function loadUsers() {
const container = document.getElementById('users');
container.innerHTML = '<p>Loading...</p>';
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
if (!res.ok) throw new Error('Failed to fetch');
const users = await res.json();
container.innerHTML = users
.map(u => `<li>${escapeHtml(u.name)}</li>`)
.join('');
} catch (err) {
container.innerHTML = '<p class="error">Could not load users</p>';
}
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
5. Form Submit Without Page Reload
Question: How do you handle form submission in a SPA-like page?
Answer: Listen for submit, call e.preventDefault(), read values via FormData or individual inputs, validate, then fetch to API. Show success/error in DOM.
6. Debounce a Search Input
Question: User types in search box. How do you avoid firing on every keystroke?
Answer: Wrap handler in debounce (300ms). Only search after user pauses typing. Mention throttle is wrong here — we want trailing edge, not rate limit.
7. Modal Accessibility Basics
Question: What DOM/UX concerns for a modal?
Answer:
- Focus moves to modal when opened
- Escape closes it
- Focus returns to trigger on close
aria-modal="true",role="dialog", label witharia-labelledby- Trap focus inside (Tab cycles within modal)
overflow: hiddenon body
8. Optimize DOM Updates for 1000 Items
Question: You need to render 1000 list items. How?
Answer:
- Use
DocumentFragmentto batch append - Virtual scrolling for very large lists (only render visible)
- Avoid
innerHTMLin a loop — build string once or use fragment - Event delegation instead of 1000 click listeners
9. localStorage for User Preferences
Question: Save theme/language preference. Implementation?
Answer:
// Save
localStorage.setItem('theme', 'dark');
// Load on init
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);
Caveats: ~5MB limit, synchronous (blocks main thread), strings only, same-origin.
10. Detect Click Outside Element
Question: Close dropdown when clicking outside. How?
Answer:
document.addEventListener('click', (e) => {
if (!dropdown.contains(e.target) && !trigger.contains(e.target)) {
dropdown.classList.remove('open');
}
});
Use capturing phase or check on document after toggle opens.
11. Lazy Load Images
Question: Load images only when they enter viewport.
Answer: Use IntersectionObserver:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
12. Custom Dropdown (No <select>)
Question: Build accessible custom select. Key concerns?
Answer: Keyboard nav (arrow keys, Enter, Escape), aria-expanded, aria-activedescendant, announce selection to screen readers. Often easier to style native <select> unless design demands custom.
13. Drag and Drop File Upload
Question: Implement drag-drop zone for files.
Answer: Listen to dragover (preventDefault to allow drop), drop event, read e.dataTransfer.files, validate type/size, preview with FileReader or upload via FormData.
14. Single Page Tab Navigation
Question: Multi-section page, nav highlights active section on scroll.
Answer: IntersectionObserver on sections OR throttled scroll listener comparing getBoundingClientRect(). Update nav active class on visible section.
15. Error Handling in DOM Apps
Question: How do you show errors to users for failed API calls?
Answer:
- Loading state (spinner/skeleton)
- Try/catch around fetch
- User-friendly message in DOM (not
alert()) - Retry button for transient failures
- Log technical details to console, not UI
Structure:
function setState({ loading, error, data }) {
loader.hidden = !loading;
errorEl.hidden = !error;
errorEl.textContent = error || '';
content.hidden = loading || !!error;
if (data) render(data);
}
How to Practice
For each question:
- Answer in 2 minutes out loud
- Mention one thing you’d do differently at scale
- Connect to a project if possible