JavaScript interviews for experienced engineers demand more than just a basic understanding of syntax and concepts. They require a deep dive into advanced topics that demonstrate your ability to solve complex problems and architect robust solutions. Whether you're aiming to advance your career or secure a new role, mastering these 20 advanced JavaScript interview questions will not only enhance your technical prowess but also set you apart from others.
function foo(){ }();
. What needs to be changed to properly make it an IIFE?IIFE stands for Immediately Invoked Function Expressions. The JavaScript parser reads function foo(){ }();
as function foo(){ }
and ();
, where the former is a function declaration and the latter is an attempt at calling a function without a name. This results in a SyntaxError
.
To fix this, wrap the function in parentheses: (function foo(){ })()
. This turns it into a function expression, allowing it to be executed immediately.
In JavaScript, iterators and generators are powerful tools for managing sequences of data and controlling the flow of execution in a more flexible way.
Iterators are objects that define a sequence and provide a next()
method to access the next value in the sequence. They are used to iterate over data structures like arrays, strings, and custom objects.
Creating a custom iterator for a range of numbers
In JavaScript, we can provide a default implementation for iterator by implementing [Symbol.iterator]()
in any custom object.
class Range {constructor(start, end) {this.start = start;this.end = end;}[Symbol.iterator]() {let current = this.start;const end = this.end;return {next() {if (current <= end) {return { value: current++, done: false };} elsereturn { value: undefined, done: true };}},};}}const range = new Range(1, 3);for (const number of range) {console.log(number); // 1, 2, 3}
Generators are a special kind of function that can pause and resume their execution, allowing them to generate a sequence of values on-the-fly. They are commonly used to create iterators but have other applications as well.
Creating an iterator using a generator function
We can rewrite our Range
example to use a generator function:
class Range {constructor(start, end) {this.start = start;this.end = end;}*[Symbol.iterator]() {let current = this.start;while (current <= this.end) {yield current++;}}}const range = new Range(1, 3);for (const number of range) {console.log(number); // 1, 2, 3}
Iterating over data streams
Generators are well-suited for iterating over data streams, such as fetching data from an API or reading files.
function* fetchDataInBatches(url, batchSize = 10) {let startIndex = 0;while (true) {const response = await fetch(`${url}?start=${startIndex}&limit=${batchSize}`);const data = await response.json();if (data.length === 0) break;yield data;startIndex += batchSize;}}const dataGenerator = fetchDataInBatches('https://api.example.com/data');for await (const batch of dataGenerator) {console.log(batch);}
Property flags and descriptors in JavaScript manage how object properties behave, allowing control over property access, modification, and inheritance.
Property flags are defined using Object.defineProperty()
. Key flags include:
writable
: Can the property be written to? Default is true
.enumerable
: Is the property enumerable? Default is true
.configurable
: Can the property be deleted or reconfigured? Default is true
.Property descriptors provide detailed information about a property, including its value and flags. Use Object.getOwnPropertyDescriptor()
to retrieve and Object.defineProperty()
to set them.
Example:
let user = { name: 'John Doe' };let descriptor = Object.getOwnPropertyDescriptor(user, 'name');console.log(descriptor); // {value: "John Doe", writable: true, enumerable: true, configurable: true}
writable
: Controls if a property can be written to. If false
, writing fails silently in non-strict mode and throws TypeError
in strict mode.
const obj = {};Object.defineProperty(obj, 'name', { writable: false, value: 'John Doe' });console.log(obj.name); // John Doeobj.name = 'Jane Doe'; // TypeError in strict mode
enumerable
: Controls if a property is visible in for...in
loops.
const obj = {};Object.defineProperty(obj, 'name', {enumerable: false,value: 'John Doe',});for (const prop in obj) console.log(prop); // No output
configurable
: Controls if a property can be deleted or reconfigured. If false
, deleting or altering fails silently in non-strict mode and throws TypeError
in strict mode.
const obj = {};Object.defineProperty(obj, 'name', {configurable: false,value: 'John Doe',});delete obj.name; // TypeError in strict mode
Polyfills are scripts that enable modern JavaScript features in older browsers that lack support, allowing developers to use the latest language features while maintaining compatibility.
Polyfills detect missing features and provide custom implementations using existing JavaScript. For example, Array.prototype.includes()
is not supported in older browsers like Internet Explorer 11:
if (!Array.prototype.includes) {Array.prototype.includes = function (searchElement) {for (var i = 0; i < this.length; i++) {if (this[i] === searchElement) return true;}return false;};}
typeof
, in
, or window
.core-js
: Provides polyfills for many ECMAScript features.
import 'core-js/actual/array/flat-map';[1, 2].flatMap((it) => [it, it]); // => [1, 1, 2, 2]
Polyfill.io: Serves polyfills based on requested features and user agents.
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
Polyfills ensure modern JavaScript features work across all browsers, enhancing compatibility and functionality.
Server-Sent Events (SSE) is a standard that allows servers to push updates to web clients over a single, long-lived HTTP connection. This enables real-time updates without the client constantly polling the server for new data.
EventSource
object, providing the URL of the server-side script that generates the event stream.event
, data
, and id
.EventSource
object receives events and dispatches them as browser events, which can be handled using event listeners.EventSource
automatically handles reconnection if the connection is lost, resuming the stream from the last received event ID.Last-Event-Id
: The client sends the Last-Event-Id
header when reconnecting, allowing the server to resume the stream.Client:
const eventSource = new EventSource('/sse');eventSource.onmessage = (event) => console.log('New message:', event.data);
Server (Node.js):
const http = require('http');http.createServer((req, res) => {if (req.url === '/sse') {// Set headers for SSEres.writeHead(200, {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache',Connection: 'keep-alive',});// Function to send a messageconst sendMessage = (message) => {res.write(`data: ${message}\n\n`); // Messages are delimited with double line breaks.};// Send a message every 5 secondsconst intervalId = setInterval(() => {sendMessage(`Current time: ${new Date().toLocaleTimeString()}`);}, 5000);// Handle client disconnectreq.on('close', () => {clearInterval(intervalId);res.end();});} else {res.writeHead(404);res.end();}}).listen(8080, () => {console.log('SSE server running on port 8080');});
SSE provides an efficient and straightforward way to push updates from a server to a client in real-time. It is well-suited for applications requiring continuous data streams but not full bidirectional communication.
JavaScript workers run scripts in background threads, offloading intensive tasks to keep the user interface responsive. There are three main types of workers in JavaScript: Web Workers / Dedicated Workers, Service Workers and Shared Workers.
postMessage()
and onmessage
.main.js
:
// Check if the browser supports workersif (window.Worker) {// Create a new Workerconst myWorker = new Worker('worker.js');// Post a message to the workermyWorker.postMessage('Hello, Worker!');// Listen for messages from the workermyWorker.onmessage = function (event) {console.log('Message from Worker:', event.data);};// Error handlingmyWorker.onerror = function (error) {console.error('Error from Worker:', error);};}
worker.js
:
// Listen for messages from the main scriptonmessage = function (event) {console.log('Message from Main Script:', event.data);// Perform a task (e.g., some computation)const result = event.data + ' - Processed by Worker';// Post the result back to the main scriptpostMessage(result);};
main.js
:
if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/service-worker.js').then((registration) => {console.log('Service Worker registered:', registration);}).catch((err) => {console.log('Service Worker registration failed:', err);});}
service-worker.js
:
self.addEventListener('fetch', (event) => {event.respondWith(caches.match(event.request).then((response) => {return response || fetch(event.request);}),);});
"use strict";
?** What are the advantages and disadvantages to using it?"use strict";
is a directive from ECMAScript 5 (ES5) that enforces stricter parsing and error handling in JavaScript, making code more secure and less error-prone.
Global Scope: Add at the beginning of a JavaScript file.
'use strict';function add(a, b) {return a + b;}
Local Scope: Add at the beginning of a function.
function myFunction() {'use strict';// Strict mode only within this function}
arguments.caller
.eval()
to prevent variable declarations in the calling scope.javascriptCopy code// Without strict modefunction defineNumber() { count = 123; }defineNumber();console.log(count); // Logs: 123// With strict mode'use strict';function strictFunc() {strictVar = 123; // ReferenceError}strictFunc();console.log(strictVar); // ReferenceError
While "use strict";
is not mandatory in these contexts, it is still recommended for older code and broader compatibility.
To secure authentication and authorization in JavaScript applications, use HTTPS to encrypt data in transit and store sensitive data like tokens securely with localStorage
or sessionStorage
. Employ token-based authentication using JWTs, validating tokens server-side. Utilize libraries like OAuth for third-party authentication and enforce role-based access control (RBAC) for proper authorization.
Minimize direct DOM access by batching changes, using documentFragment
, and leveraging virtual DOM libraries like React. Use requestAnimationFrame
for animations and avoid layout thrashing by separating DOM reads and writes.
Minimize the number of requests, use caching, compress data, and leverage HTTP/2 and service workers. Combine CSS files, use Cache-Control
headers for static assets, and enable Gzip compression to reduce data size.
Prevent clickjacking by using the X-Frame-Options HTTP header set to DENY or SAMEORIGIN to control iframe embedding. Additionally, use the Content-Security-Policy header with the frame-ancestors directive to specify allowed origins.
X-Frame-Options: DENYContent-Security-Policy: frame-ancestors 'self'
Use the Constraint Validation API with properties like validity
and validationMessage
, and methods like checkValidity()
and setCustomValidity()
. For example:
const input = document.querySelector('input');if (input.checkValidity()) {console.log('Input is valid');} else {console.log(input.validationMessage);}
In JavaScript, hoisting moves function declarations to the top of their scope, making them callable before their definition. Function expressions are not hoisted similarly; the variable is hoisted, but its assignment is not.
// Function declarationconsole.log(foo()); // Works finefunction foo() {return 'Hello';}// Function expressionconsole.log(bar()); // Throws TypeError: bar is not a functionvar bar = function () {return 'Hello';};
JavaScript uses automatic garbage collection to reclaim memory from objects and variables no longer in use. The two main algorithms are mark-and-sweep and generational garbage collection.
Mark-and-Sweep
Generational Garbage Collection Modern engines divide objects into generations based on age and usage. Frequently accessed objects stay in younger generations, while less-used objects move to older generations, optimizing garbage collection by focusing on short-lived objects.
Different JavaScript engines may use different garbage collection strategies.
Mocks and stubs simulate real objects in testing. Stubs provide predefined responses to function calls, isolating the code being tested from external dependencies. Mocks are more complex, verifying interactions like whether a function was called and with what arguments. Stubs focus on isolating functionality, while mocks ensure correct interaction with dependencies.
A proxy in JavaScript is an intermediary object that intercepts and customizes operations on another object, such as property access, assignment, and function invocation.
Example:
const myObject = {name: 'John',age: 42,};const handler = {get: function (target, prop) {console.log(`Accessed property "${prop}"`);return target[prop];},};const proxiedObject = new Proxy(myObject, handler);console.log(proxiedObject.name); // Logs: 'John'// Accessed property "name"console.log(proxiedObject.age); // Logs: 42// Accessed property "age"
Use cases:
Using languages like TypeScript or CoffeeScript, which compile to JavaScript, has several pros and cons.
Advantages:
Disadvantages:
requestAnimationFrame
for synchronized animations.will-change
to elements that change frequently.Tools such as Chrome DevTools, Lighthouse, WebPageTest, and JSPerf are commonly used for this purpose. Chrome DevTools includes a Performance panel for profiling, Lighthouse provides performance audits, WebPageTest offers detailed performance testing, and JSPerf aids in comparing JavaScript snippet performance.
Web Workers enable running JavaScript in the background, independent of the main execution thread of a web application. This is beneficial for handling intensive computations without blocking the user interface. Web Workers are created using the Worker
constructor and communication with them is facilitated through the postMessage
and onmessage
methods.
Preparing yourself to answer these questions in an interview setting will certainly help you stand out from the crowd. It's not just about knowing the answers; it's about understanding the underlying concepts and applying them effectively in real-world scenarios. Mastering these advanced JavaScript topics will not only boost your confidence during technical interviews but also equip you to build scalable and efficient web applications.