Coding Patterns

⭐ Interview Importance: HIGH
⏱️ Revision Time: 4 min

TL;DR

Interviewers evaluate not just if your code works, but if it is idiomatic to Node.js. This means understanding asynchronous control flow, the Observer pattern (Event Emitters), Factory functions, and Streams.

1. The Promisify Pattern

Interviewers often ask you to convert an older callback-based API into a modern Promise-based API without using the built-in util.promisify.

// The legacy callback function
function legacyReadFile(path, callback) {
    // fs.readFile(path, callback)
}

// THE PATTERN
function readFileAsync(path) {
    return new Promise((resolve, reject) => {
        legacyReadFile(path, (err, data) => {
            if (err) return reject(err);
            resolve(data);
        });
    });
}

2. The Singleton Pattern (via Module Caching)

Because Node.js caches modules, the simplest and most common way to implement a Singleton (like a database connection pool) is just to export an instance.

// db.js
class Database {
    constructor() {
        this.connection = this.connect();
    }
    // ...
}
// THE PATTERN: Export the INSTANCE, not the class.
module.exports = new Database(); 

3. The Middleware / Chain of Responsibility Pattern

You might be asked to implement a basic version of Express middleware logic from scratch.

class App {
    constructor() { this.middlewares = []; }
    
    use(fn) { this.middlewares.push(fn); }
    
    run(req) {
        let index = 0;
        // The 'next' function recursively calls the next middleware
        const next = () => {
            if (index < this.middlewares.length) {
                const middleware = this.middlewares[index++];
                middleware(req, next);
            }
        };
        next();
    }
}

4. The Observer Pattern

Inter-module communication in Node.js is often decoupled using Event Emitters instead of tight callbacks.

const EventEmitter = require('events');

class PaymentProcessor extends EventEmitter {
    process(userId) {
        // ... logic ...
        this.emit('paymentSuccess', userId);
    }
}

const processor = new PaymentProcessor();
// Decoupled logic
processor.on('paymentSuccess', (id) => sendEmail(id));
processor.on('paymentSuccess', (id) => updateAnalytics(id));

Common Interview Questions

Why favor Factory Functions over ES6 Classes in some Node.js codebases?

Factory functions don’t require the new keyword and don’t suffer from this context binding issues (which frequently cause bugs when passing class methods as callbacks or into Promise chains). They also allow for true private variables via closures, whereas classes required # prefixes or weak maps historically.