- //continuation from part 2
21. Array Dispatch Event on Push
Interviewer: "How would you create an array that emits events whenever elements are pushed to it?"
Candidate: "I'll create a custom array class that extends the built-in Array and dispatches events:
class ObservableArray extends Array {
constructor(...args) {
super(...args);
this.eventTarget = new EventTarget();
}
push(...items) {
const result = super.push(...items);
const event = new CustomEvent('push', {
detail: { items, newLength: this.length }
});
this.eventTarget.dispatchEvent(event);
return result;
}
addEventListener(type, callback) {
this.eventTarget.addEventListener(type, callback);
}
}
// Usage
const arr = new ObservableArray();
arr.addEventListener('push', (e) => {
console.log('Added items:', e.detail.items);
console.log('New length:', e.detail.newLength);
});
arr.push(1, 2, 3);
This pattern is useful for reactive programming scenarios where you need to respond to data changes."
22. Deep Clone Object
Interviewer: "How would you implement a deep clone function that handles circular references?"
Candidate: "Here's a solution using a WeakMap to handle circular references:
function deepClone(obj, visited = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (visited.has(obj)) {
return visited.get(obj);
}
const clone = Array.isArray(obj) ? [] : {};
visited.set(obj, clone);
Object.entries(obj).forEach(([key, value]) => {
clone[key] = deepClone(value, visited);
});
return clone;
}
// Example usage
const original = {
name: 'John',
address: { city: 'New York' }
};
original.self = original; // Circular reference
const cloned = deepClone(original);
The WeakMap prevents infinite recursion with circular references while properly copying all nested structures."
23. JSON Stringify Implementation
Interviewer: "Can you implement a basic version of JSON.stringify?"
Candidate: "I'll implement a simplified version that handles common types:
function customStringify(value) {
// Handle primitive types
if (value === null) return 'null';
if (typeof value === 'string') return `"${value}"`;
if (typeof value === 'number' || typeof value === 'boolean') {
return String(value);
}
// Handle arrays
if (Array.isArray(value)) {
const elements = value.map(item =>
item === undefined ? 'null' : customStringify(item)
);
return `[${elements.join(',')}]`;
}
// Handle objects
if (typeof value === 'object') {
const pairs = Object.entries(value)
.filter(([_, val]) => val !== undefined)
.map(([key, val]) =>
`"${key}":${customStringify(val)}`
);
return `{${pairs.join(',')}}`;
}
}
// Example
const obj = {
name: 'John',
age: 30,
hobbies: ['reading', null, undefined]
};
console.log(customStringify(obj));
This covers the main functionality of JSON.stringify, though the real implementation has additional features like the replacer function."
24. React DOM Render
Interviewer: "Can you explain how React's DOM rendering works and implement a simple version?"
Candidate: "I'll create a basic version that handles elements and text nodes:
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.flat()
}
};
}
function render(element, container) {
// Handle text nodes
if (typeof element === 'string' || typeof element === 'number') {
container.appendChild(document.createTextNode(element));
return;
}
// Create DOM element
const dom = document.createElement(element.type);
// Add properties
Object.entries(element.props || {})
.filter(([key]) => key !== 'children')
.forEach(([key, value]) => {
if (key.startsWith('on')) {
dom.addEventListener(key.toLowerCase().slice(2), value);
} else {
dom.setAttribute(key, value);
}
});
// Render children
element.props.children.forEach(child => render(child, dom));
container.appendChild(dom);
}
// Usage
const element = createElement('div', { class: 'container' },
createElement('h1', null, 'Hello'),
createElement('p', null, 'World')
);
render(element, document.getElementById('root'));
This is a simplified version that shows the core concepts of how React creates and updates DOM elements."
25. Retry Promise N Times
Interviewer: "Implement a function that retries a promise a specified number of times before failing."
Candidate: "Here's a solution with exponential backoff:
async function retryPromise(fn, retries, delay = 1000, multiplier = 2) {
try {
return await fn();
} catch (error) {
if (retries === 0) throw error;
await new Promise(resolve => setTimeout(resolve, delay));
return retryPromise(
fn,
retries - 1,
delay * multiplier,
multiplier
);
}
}
// Usage
const fetchWithRetry = () =>
retryPromise(
() => fetch('https://api.example.com/data'),
3, // retry 3 times
1000 // start with 1s delay
);
This is particularly useful for handling transient failures in network requests or other unreliable operations."
26. Extended Event Emitter
Interviewer: "Can you implement an event emitter with support for wildcards and once listeners?"
Candidate: "Here's an enhanced event emitter implementation:
class EventEmitter {
constructor() {
this.events = new Map();
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event).add(callback);
return () => this.off(event, callback);
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
return this.on(event, wrapper);
}
emit(event, ...args) {
const callbacks = this.events.get(event);
if (callbacks) {
callbacks.forEach(cb => cb(...args));
}
// Handle wildcards
if (event !== '*') {
this.emit('*', event, ...args);
}
}
off(event, callback) {
const callbacks = this.events.get(event);
if (callbacks) {
callbacks.delete(callback);
}
}
}
// Usage
const emitter = new EventEmitter();
const unsubscribe = emitter.once('user:login', user => {
console.log('User logged in:', user);
});
This implementation supports both one-time listeners and wildcard events, making it more flexible for complex applications."
27. Promise.all Implementation
Interviewer: "Can you implement your own version of Promise.all?"
Candidate: "Here's an implementation that maintains the same behavior as the native Promise.all:
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
}
// Usage
const promises = [
Promise.resolve(1),
Promise.resolve(2),
new Promise(resolve => setTimeout(() => resolve(3), 1000))
];
promiseAll(promises).then(console.log);
This implementation maintains the key features of Promise.all:
Resolves when all promises resolve
Rejects immediately if any promise rejects
Preserves the order of results"
Remember in interviews to:
Discuss edge cases
Talk about potential optimizations
Mention real-world applications
Be ready to handle follow-up questions about error handling and edge cases
This completes Part 3 of our JavaScript interview guide. Stay tuned for Part 4 where we'll cover more advanced patterns and optimizations!