JavaScript Interview Questions for 5+ Years Experience - Interview Questions and Answers
- Use module boundaries, lazy-loading, separation of concerns, code-splitting, state management boundaries (e.g., Redux slices/domains).
- Devise a chunk loading strategy, caching, SSR/CSR options, and performance budgets.
- Include error boundaries, logging, and A/B feature flags.
Start with automated conversion, incremental class adoption via codemods, maintain behavior parity with tests, slowly remove Prototype-based APIs, adopt bundler for ESM.
Use performance profiling → pinpoint reflows (excessive DOM updates), deep diff cycles; fix by virtualization (windowing), memoizing list items, using requestAnimationFrame, batching updates, optimizing render functions.
Use Service Workers, IndexedDB, background sync. Version items with timestamps, use CRDT or last-write-wins, resolve conflicts in sync layer, queue operations while offline.
Use window.onerror
, window.addEventListener('unhandledrejection')
, custom error classes, centralized logging to backend, fallback UI/error boundaries, retry logic, circuit breaker.
Sanitize user input, use CSP, escape dynamic DOM insertion, avoid innerHTML
. For objects: deep-freeze, avoid untrusted Object.assign
, use Object.create(null)
for maps, lock prototypes via Object.freeze(Object.prototype)
.
Delegate to Web Workers, use TypedArrays or Offload computations to wasm or transferable object, update UI via postMessage, show intermediate progress indicators.
Use schema-driven forms, validate using Constraint Validation API or schema (Yup), debounce validation, lazy load validation rules, isolate form state, render dynamically with virtualized inputs.
Use modular architecture with ES modules or a monorepo approach. Employ:
- Feature-based folder structure
- Component isolation with clear APIs
- Shared libraries
- Linting, testing, and CI/CD pipelines
- Code ownership per team/module
Also apply Micro-Frontend Architecture if the teams are highly autonomous.
- Constructor Function: Uses
new
, tightly coupled with class semantics. - Factory Function: Returns objects, can close over private data, avoids
this
, more testable.
Prefer factory functions in larger apps for better testability and encapsulation unless class inheritance is crucial.
- Use promisify (Node.js
util.promisify
) or manually wrap callbacks into Promises. - Apply
async/await
to make the flow readable. - Break code into smaller functions.
- Use named functions instead of anonymous for clarity.
- Profile using Chrome DevTools (Performance tab)
- Use lazy loading, code-splitting, and memoization
- Avoid unnecessary re-renders (React:
React.memo
,useMemo
) - Minimize DOM operations and use
requestAnimationFrame
for animations - Debounce or throttle expensive operations
- Avoid polling, use WebSockets
- Use virtual DOM or virtual scrolling
- Implement client-side caching
- Use service workers for offline and caching
- Reduce bundle size (tree-shaking, compression)
- Build a shared NPM package (internal registry)
- Use TypeScript for typings and safety
- Version with SemVer
- Write documentation and add tests
- Use Chrome DevTools > Performance > Memory
- Look for detached DOM nodes or retained closures
- Check for global variables or event listeners not being cleaned
- Use
WeakMap
orWeakRef
to avoid strong references
Occurs when long-running tasks (e.g., while loops) block the main thread. Prevent it by:
- Splitting heavy tasks using
setTimeout
,requestIdleCallback
, orWeb Workers
- Use libraries like Immutable.js or Immer
- Avoid direct state mutation
- Use object spread or
Object.assign
- Enforce via linters and TypeScript
- SSR: Better SEO, faster first paint
- CSR: Better interactivity, control
Use SSR for landing pages, marketing, or content-heavy pages. CSR for apps with dynamic content after login.
Use Chrome DevTools → Memory tab → take heap snapshots. Look for:
- Detached DOM nodes
- Objects not being garbage collected
- Closures retaining references
Fix by cleaning up timers, event listeners, and circular references.
It happens when removed DOM elements are still referenced in JS (e.g., event handlers not cleaned). Use removeEventListener
or WeakRef
.
Event loop processes:
- Microtasks (e.g.,
Promise.then
,MutationObserver
) before next macrotask - Macrotasks (e.g.,
setTimeout
,setInterval
) executed after microtasks
This order affects async behavior.
Use:
- Web Workers for CPU-heavy tasks
setTimeout
orrequestIdleCallback
to chunk tasks- Offload to backend if needed
- XSS (Cross-site scripting): Sanitize user inputs
- CSRF: Use tokens
- CSP: Apply Content Security Policy
- Avoid
eval
, usestrict mode
, validate inputs
Use centralized state management (e.g., Redux, Zustand), with immutability and selectors. Modularize state slices per feature.
null
: Intentional absence of valueundefined
: Uninitialized or missing valuevoid 0
: Always returnsundefined
, used to protect against reassignedundefined
- Debounce: Fires once after inactivity (e.g., search input)
- Throttle: Limits rate of calls (e.g., scroll, resize events)
Use Webpack 5 Module Federation plugin:
- Expose and consume components remotely
- Useful in micro-frontend architectures
localStorage
: Persistent, string-based, 5–10 MBsessionStorage
: Per-tab onlyIndexedDB
: Structured data, asynchronous, large capacity
In JS, it's often implemented via custom events or EventEmitter
.
class Subject {
observers = [];
subscribe(fn) { this.observers.push(fn); }
notify(data) { this.observers.forEach(fn => fn(data)); }
}
They hold weak references, allowing GC of keys when not used elsewhere — ideal for metadata or private data in classes.
JS runs on a single-threaded event loop. Concurrency is simulated using callbacks, promises, and async/await
via the event loop.
Use frameworks like Jest/Mocha with done
callback, async/await
, or return a Promise from the test function.
Caused by uncontrolled recursion or infinite loops. Fix by:
- Using iteration
- Limiting recursion depth
- Using tail-call optimization (if supported)
Proxy
lets you intercept and define custom behavior for fundamental operations (e.g., get, set). Used for:
- Validation
- Tracing
- Virtualization
- Reactive state (Vue.js)
A recursive function that returns another function’s result directly can reuse stack. Limited browser support (mostly non-strict mode).
Variable and function declarations are moved to the top of their scope.
var
is hoisted and initialized asundefined
let
/const
are hoisted but uninitialized (TDZ)
It's the phase between entering the scope and variable declaration where accessing a let
/const
variable throws a ReferenceError.
- Create unique property keys (avoid collisions)
- Implement internal object behavior (e.g., iterators,
Symbol.iterator
)
A closure is a function that retains access to its lexical scope even when the outer function has finished execution.
Real-world uses:
- Data privacy (e.g., counter module)
- Currying
- Memoization
- Event handlers in loops
- Use
Promise.all
cautiously - Locking mechanisms or semaphores
- Implement queue-based execution
- Avoid shared mutable state in async functions
Use:
- Chrome DevTools → Performance → Heap snapshot
- Lighthouse for memory
- Third-party tools like
clinic.js
Simulate traffic using automated test scripts
Use Object.defineProperty
to define non-enumerable, read-only, or getter/setter-based properties for controlled access.
- Use schema-based validation (e.g., Yup)
- Controlled components with React
- Avoid deep nesting in state
- Use custom hooks for reusability
- Disable button immediately after first click
- Debounce or throttle click handler
- Track state to ignore repeat clicks
Service workers are background scripts that intercept network requests for caching and offline support.
Used in:
- PWA
- Background sync
- Push notifications
- Configure CORS on backend
- Use JSONP (legacy) or proxies
- With credentials:
fetch(url, { credentials: 'include' })
Generators (function*
) yield values lazily.
Used in:
- Custom iterators
- Asynchronous control flows
- Pause/resume execution scenarios
call
: Invokes function with argumentsapply
: Same as call but uses arraybind
: Returns new function with bound context
It prevents runtime errors when accessing deep nested objects that might be undefined
or null
.
user?.address?.city // won't throw if user or address is undefined
Use Map
when:
- Keys are not strings
- Need insertion order
- High-frequency lookups with complex keys
Modules (ESM
, CommonJS
) allow encapsulated code, reusability, and dependency management, which improves maintainability in large codebases.
setTimeout
and setInterval
are managed via the Web APIs and queued into the event loop once the delay is complete. They are not precise timers.
- Use virtualization (e.g.,
react-window
) - Debounce scroll events
- Avoid re-rendering the full list
- Use
requestAnimationFrame
- Chrome DevTools → Performance tab
- Use
console.time
,Performance.now()
- Identify bottlenecks (layout thrashing, loops)
- Never expose secrets in frontend
- Use short-lived access tokens
- Store in memory (avoid localStorage)
- Use HTTP-only secure cookies
==
: Type coercion comparison===
: Strict equality
Use ===
always to avoid unexpected coercion.
structuredClone()
(modern)JSON.parse(JSON.stringify(obj))
(limited)- Recursive custom function or
lodash.cloneDeep
class EventBus {
events = {};
on(event, fn) { (this.events[event] ||= []).push(fn); }
emit(event, data) { (this.events[event] || []).forEach(fn => fn(data)); }
}
- Batch DOM changes
- Use
DocumentFragment
- Minimize reflows
- Avoid layout thrashing
- Use
IntersectionObserver
- Dynamically import components/modules
loading="lazy"
for images
Transforming a function with multiple arguments into a sequence of unary functions.
Used in functional programming and reusability.
const add = x => y => x + y;
- Use error tracking (Sentry, Rollbar)
- Add logs and replay sessions
- Use source maps
- Debug based on user data, network logs
- Use local packages or libraries
- npm workspaces, Lerna, or Nx
- Follow semantic versioning internally
Use Chrome DevTools Memory tab to take heap snapshots. Look for detached DOM nodes or retained closures. Check for non-cleaned subscriptions in useEffect
, unremoved event listeners, and unreleased intervals/timers. Profile components with React DevTools.
By using the latest ref pattern:
const latestValue = useRef(value);
useEffect(() => {
latestValue.current = value;
}, [value]);
Avoid depending on old values in closures by referring to latestValue.current
inside callbacks.
Use differential bundling with tools like Vite/Rollup/Webpack + Babel. Serve modern ES modules (type="module"
) to new browsers and legacy bundles (with nomodule
) to older ones.
Code splitting with dynamic import()
, tree-shaking unused code, compressing assets (Gzip/Brotli), lazy loading non-critical routes/components, using HTTP/2 for parallel downloads.
Microtasks (Promise.then
, queueMicrotask
) run before rendering and have higher priority. Macrotasks (setTimeout
, setInterval
) run after. Improper microtask usage can block rendering.
When you want fine-grained control (e.g., making a property read-only, non-enumerable, or setting custom getter/setter logic), such as enforcing internal encapsulation.
JS might be storing session data in-memory (e.g., a token in a variable or non-persistent storage like sessionStorage
). Upon tab refresh or inactivity, it gets lost. Use localStorage
with expiration logic or rely on HTTP-only secure cookies.
Use a leading-edge debounce function (e.g., Lodash's _.debounce
with { leading: true, trailing: false }
).
Never inject raw user content into HTML. Use DOM APIs (textContent
), sanitize inputs using libraries (e.g., DOMPurify), and enforce CSP headers on the server.
Service workers enable caching via the Cache API, intercept requests, and serve assets offline or from cache, improving performance and reliability in flaky networks.
this
may not refer to the intended context. Solutions include using arrow functions (lexical this
), bind(this)
in constructors, or class fields in modern JS.
Use DevTools Performance tab: record runtime, look for long tasks, layout thrashing, reflows, expensive paint times, and JS call stacks. Use Lighthouse to audit performance.
Use React.memo
to prevent unnecessary re-renders. Profile with React DevTools. Check useEffect
dependencies. Avoid inline objects/arrays/functions passed as props.
Batch DOM changes using DocumentFragments or requestAnimationFrame
. Avoid layout thrashing. Use transform
and opacity
for animations (they don’t trigger reflow).
Though ES6 specifies TCO, most modern JS engines (like V8/Chrome) don’t support it due to debugging complexities. TCO allows recursive functions to avoid growing the call stack.
It throws an error on circular references. Use a replacer function or libraries like flatted
to handle them gracefully.
Use structured cloning (structuredClone(obj)
) or libraries like Lodash _.cloneDeep
. Avoid JSON.stringify
for complex types (functions, Date, undefined).
Capture stack traces using window.onerror
or ErrorBoundary
(React). Use console.trace()
, source maps, and runtime logs to trace user steps. Automate with Sentry or Bugsnag.
Storing metadata tied to DOM nodes or objects without preventing garbage collection (e.g., memoization caches, internal component state).
forEach
doesn’t await async functions. Use for...of
with await
, or Promise.all()
with map()
for parallel processing.
Use IntersectionObserver API to detect when the sentinel element is visible and trigger more content loading.
Regularly take heap snapshots, monitor detached DOM nodes, use WeakMap
where appropriate, and audit long-held event listeners.
Refactor using async/await
, break into reusable async functions, or use Promises with chaining. Consider using RxJS for complex async flows.
Use Module Federation in Webpack 5 with shared singleton dependencies, or external CDNs with careful version pinning and semantic versioning.
It makes the object immutable at the top level. It’s shallow — nested objects can still be modified unless recursively frozen.
eval()
executes arbitrary code, introducing XSS vulnerabilities, blocking JS optimizations, and potentially breaking scope chains.
Array.prototype.myFlat = function(depth = 1) {
return this.reduce((acc, val) =>
acc.concat(Array.isArray(val) && depth > 1 ? val.myFlat(depth - 1) : val), []);
};
Use ESLint rules (no-console
) in shared config, CI integration to fail builds, and optionally strip logs using Babel plugins or Webpack production mode.
If closures capture variables or DOM nodes that are never released (e.g., through long-lived timers or event listeners), they prevent GC from cleaning up.
Avoid storing in localStorage
/sessionStorage
for critical secrets. Prefer secure HTTP-only cookies with same-site settings. Always validate on server.
requestAnimationFrame
syncs with display refresh (usually 60fps), is more efficient and prevents layout jank. setTimeout
is inaccurate due to delay drift.
It means JS is blocking the main thread for too long. Optimize long tasks, break large bundles, use code splitting, and reduce JS execution time.
The TDZ (Temporal Dead Zone) is the time between let
/const
declaration and initialization. Accessing variables in the TDZ throws a ReferenceError.
Used Proxy to intercept state changes in a data store for validation, auto-saving, or reactivity (like Vue does internally).
Use AbortController to cancel previous fetches. Track request IDs to ignore outdated responses. Use useEffect
cleanup in React.
Use context API for shared global data. For more complex apps, use state libraries like Redux, Zustand, Jotai, or even RxJS.
The reconciler compares the virtual DOM trees and figures out the minimum number of DOM changes using keys and heuristics. It works with the renderer (e.g., DOM, native).
For controlled iteration (e.g., pagination), handling async flows via redux-saga
, or simulating tasks like polling or streaming.
Use throttling (_.throttle
) or track last click timestamp and disable the button temporarily.
A ==
comparison between 0
and ''
evaluated true unexpectedly. Solved by always using ===
and applying lint rules.
Reduces readability and increases error propagation complexity. Use async/await, flatten control flow, or use error boundaries.
Used Web Workers for heavy data parsing or processing (e.g., CSV to JSON) to avoid UI blocking. Communicated via postMessage
.
Part of Web Components. You can define reusable elements using customElements.define
. Useful for shared UI across frameworks.
img.onload = () => console.log('Loaded!');
img.onerror = () => console.error('Failed!');
Module boundaries, shared utils, ESLint/Prettier, TypeScript, CI/CD, proper folder structure, API schemas (OpenAPI), performance budgets.
Use requestIdleCallback
, or defer with setTimeout(fn, 0)
, or split bundles and load async via import()
.
Use Performance API (performance.getEntriesByType('script')
) or monitor parse/compile timings via Lighthouse or Chrome DevTools.
Read DOM once, store values in variables, apply writes later. Avoid interleaving reads and writes.
Understand it’s placed in the macrotask queue. The actual execution waits for the call stack to clear and other tasks to finish.
Include API version in URL (/api/v2/...
), or use custom headers. Abstract logic in a version-aware API client.
Tutorials
Random Blogs
- Python Challenging Programming Exercises Part 3
- Understanding AI, ML, Data Science, and More: A Beginner's Guide to Choosing Your Career Path
- 15 Amazing Keyword Research Tools You Should Explore
- Extract RGB Color From a Image Using CV2
- Understanding OLTP vs OLAP Databases: How SQL Handles Query Optimization
- OLTP vs. OLAP Databases: Advanced Insights and Query Optimization Techniques
- Understanding Data Lake, Data Warehouse, Data Mart, and Data Lakehouse – And Why We Need Them
- 5 Ways Use Jupyter Notebook Online Free of Cost
- How to Become a Good Data Scientist ?
- Role of Digital Marketing Services to Uplift Online business of Company and Beat Its Competitors