JavaScript Interview Guide Part 4: Advanced Patterns and Performance Optimization

JavaScript Interview Guide Part 4: Advanced Patterns and Performance Optimization

ยท

4 min read

  • //continuation from part 3

28. Promise.race Implementation

Interviewer: "Can you implement your own version of Promise.race?"

Candidate: "Here's an implementation that mirrors the native Promise.race behavior:

function promiseRace(promises) {
    return new Promise((resolve, reject) => {
        promises.forEach(promise => {
            Promise.resolve(promise).then(resolve).catch(reject);
        });
    });
}

// Example usage
const promises = [
    new Promise(resolve => setTimeout(() => resolve('First'), 500)),
    new Promise(resolve => setTimeout(() => resolve('Second'), 200)),
    new Promise((_, reject) => setTimeout(() => reject('Error'), 300))
];

promiseRace(promises)
    .then(console.log)
    .catch(console.error);

The key point here is that it resolves or rejects with the first promise that settles, just like the native implementation."

29. Promise.any Implementation

Interviewer: "How would you implement Promise.any?"

Candidate: "Here's how I'd implement Promise.any which resolves with the first successful promise:

function promiseAny(promises) {
    return new Promise((resolve, reject) => {
        let errors = [];
        let pending = promises.length;

        if (pending === 0) {
            reject(new AggregateError([], 'No promises to resolve'));
            return;
        }

        promises.forEach((promise, index) => {
            Promise.resolve(promise)
                .then(resolve)
                .catch(error => {
                    errors[index] = error;
                    pending--;

                    if (pending === 0) {
                        reject(new AggregateError(errors));
                    }
                });
        });
    });
}

// Usage
const promises = [
    Promise.reject('Error 1'),
    Promise.resolve('Success'),
    Promise.reject('Error 2')
];

promiseAny(promises).then(console.log);

Unlike Promise.race, this waits for the first success rather than the first settlement."

30. Promise.allSettled Implementation

Interviewer: "Can you implement Promise.allSettled?"

Candidate: "Here's an implementation that handles both fulfilled and rejected promises:

function promiseAllSettled(promises) {
    return Promise.all(
        promises.map(promise => 
            Promise.resolve(promise)
                .then(value => ({
                    status: 'fulfilled',
                    value
                }))
                .catch(reason => ({
                    status: 'rejected',
                    reason
                }))
        )
    );
}

// Example usage
const promises = [
    Promise.resolve(1),
    Promise.reject('error'),
    Promise.resolve(3)
];

promiseAllSettled(promises).then(results => {
    results.forEach(result => {
        if (result.status === 'fulfilled') {
            console.log('Success:', result.value);
        } else {
            console.log('Error:', result.reason);
        }
    });
});

This gives us the status of all promises regardless of whether they succeeded or failed."

31. Clear All Timeouts

Interviewer: "How would you implement a function to clear all existing timeouts?"

Candidate: "Here's a solution that tracks and clears all timeouts:

const timeoutIds = new Set();

// Override setTimeout
const originalSetTimeout = window.setTimeout;
window.setTimeout = function(callback, delay, ...args) {
    const id = originalSetTimeout(callback, delay, ...args);
    timeoutIds.add(id);
    return id;
};

function clearAllTimeouts() {
    timeoutIds.forEach(id => {
        clearTimeout(id);
        timeoutIds.delete(id);
    });
}

// Usage
setTimeout(() => console.log('1'), 1000);
setTimeout(() => console.log('2'), 2000);
clearAllTimeouts(); // Clears all pending timeouts

This is useful for cleanup in SPA applications or when transitioning between different states."

32. Memoization Implementation

Interviewer: "Can you implement a memoization function with cache size limit?"

Candidate: "I'll implement a memoize function with LRU cache:

function memoize(fn, { maxSize = 100 } = {}) {
    const cache = new Map();

    return function(...args) {
        const key = JSON.stringify(args);

        if (cache.has(key)) {
            // Move to most recently used
            const value = cache.get(key);
            cache.delete(key);
            cache.set(key, value);
            return value;
        }

        const result = fn.apply(this, args);

        if (cache.size >= maxSize) {
            // Remove least recently used
            const firstKey = cache.keys().next().value;
            cache.delete(firstKey);
        }

        cache.set(key, result);
        return result;
    };
}

// Usage
const expensiveOperation = memoize(
    (n) => {
        console.log('Computing...');
        return n * 2;
    },
    { maxSize: 2 }
);

This implementation provides both caching and memory management."

33. Async Progress Bar

Interviewer: "How would you implement an async progress bar for multiple operations?"

Candidate: "Here's a simple implementation that tracks multiple async operations:

class ProgressTracker {
    constructor(total) {
        this.total = total;
        this.completed = 0;
        this.listeners = new Set();
    }

    onProgress(callback) {
        this.listeners.add(callback);
        return () => this.listeners.delete(callback);
    }

    increment() {
        this.completed++;
        const progress = (this.completed / this.total) * 100;
        this.listeners.forEach(cb => cb(progress));
    }
}

// Usage
async function uploadFiles(files) {
    const tracker = new ProgressTracker(files.length);

    tracker.onProgress(progress => {
        console.log(`Upload progress: ${progress}%`);
    });

    await Promise.all(files.map(async file => {
        await uploadFile(file);
        tracker.increment();
    }));
}

This provides a reusable way to track progress across multiple async operations."

34. GroupBy Polyfill

Interviewer: "Can you implement a polyfill for Array.prototype.groupBy?"

Candidate: "Here's a polyfill that matches the TC39 proposal:

if (!Array.prototype.groupBy) {
    Array.prototype.groupBy = function(keyFn) {
        return this.reduce((groups, item) => {
            const key = keyFn(item);
            if (!groups[key]) {
                groups[key] = [];
            }
            groups[key].push(item);
            return groups;
        }, {});
    };
}

// Usage example
const inventory = [
    { name: 'apple', type: 'fruit' },
    { name: 'carrot', type: 'vegetable' },
    { name: 'banana', type: 'fruit' }
];

const grouped = inventory.groupBy(item => item.type);
/* Result:
{
    fruit: [
        { name: 'apple', type: 'fruit' },
        { name: 'banana', type: 'fruit' }
    ],
    vegetable: [
        { name: 'carrot', type: 'vegetable' }
    ]
}
*/

This implementation follows the proposed standard while maintaining compatibility."

Key Interview Tips for Part 4:

  1. Focus on explaining performance implications

  2. Be ready to discuss browser compatibility

  3. Mention error handling and edge cases

  4. Consider memory management

  5. Be prepared to discuss real-world applications

This completes our four-part JavaScript interview preparation series. Good luck with your interviews! ๐Ÿš€

ย