I. FOUNDATIONAL CORE & RUNTIME DEEP DIVES
1. Event Loop & Asynchronous Programming
Question: "Walk me through the Node.js event loop phases. How does setImmediate() differ from process.nextTick(), and where would you use each in a real-world scenario?"
Expected Answer:
The Node.js event loop operates in these primary phases:
Timers: Executes callbacks scheduled by
setTimeout()andsetInterval()Pending Callbacks: Executes I/O callbacks deferred to the next loop iteration
Poll: Retrieves new I/O events and executes I/O-related callbacks
Check: Executes
setImmediate()callbacksClose Callbacks: Executes close event callbacks (e.g.,
socket.on('close'))
Key Distinctions:
process.nextTick(): Not part of the event loop; adds callback to the next tick queue, which processes before the event loop continues. Use for:// Ensuring API consistency in constructor function MyClass() { process.nextTick(() => { this.emit('initialized'); }); }
setImmediate(): Executes in the Check phase. Use for:// Deferring CPU-intensive work after I/O fs.readFile('large.json', (err, data) => { setImmediate(() => { const parsed = JSON.parse(data); // Heavy CPU processResult(parsed); }); });
Real-World Pattern: In HTTP servers, use setImmediate() for logging after sending response, ensuring client doesn't wait:
res.end('Data sent'); setImmediate(() => { auditLog(req, res); });
2. Memory Management & Performance
Question: "Your Node.js application shows increasing memory usage over time. Describe your debugging methodology and explain how V8's garbage collection works for different object types."
Expected Answer:
Debugging Methodology:
Monitor: Use
process.memoryUsage()and--inspectflag with Chrome DevToolsHeap Snapshots: Compare snapshots to identify retained objects
CPU Profiling: Identify memory allocation hot spots
Production Monitoring: Use APM tools (New Relic, Datadog) with heap profiling
V8 Memory Structure:
New Space (Scavenger): Young generation (1-8MB). Fast, minor GC
Old Space: Long-lived objects. Mark-sweep-compact major GC
Large Object Space: >1MB objects, never moved
Code Space: JIT compiled code
Map Space: Hidden classes/C++ objects
Common Leaks & Solutions:
// LEAK: Closure capturing large objects function createLeak() { const large = new Array(1000000).fill('*'); return () => console.log(large.length); // large persists! } // FIX: Null references function createSafe() { const large = new Array(1000000).fill('*'); const length = large.length; large = null; // Explicit dereference return () => console.log(length); }
Advanced: Use WeakMap for caches, stream large datasets, implement connection pooling.
II. SCALING & ARCHITECTURE PATTERNS
3. Cluster Module & Worker Threads
Question: "When would you use the Cluster module vs Worker Threads vs child processes? Design a system that processes 10,000 PDF files daily."
Expected Answer:
| Technology | Use Case | Memory | IPC | Restart |
|---|---|---|---|---|
| Cluster | HTTP load balancing | Isolated | Messages | Automatic |
| Worker Threads | CPU-intensive JS tasks | Shared | Transferable objects | Manual |
| Child Process | External binaries/legacy | Isolated | Streams | Manual |
PDF Processing System Design:
// Master process (orchestration) const { Worker, isMainThread, parentPort } = require('worker_threads'); const { cpus } = require('os'); class PDFProcessor { constructor() { this.workerPool = []; this.taskQueue = []; } async processBatch(files) { const concurrency = Math.min(cpus().length - 1, 4); // Create worker pool for (let i = 0; i < concurrency; i++) { const worker = new Worker('./pdf-worker.js', { workerData: { id: i }, resourceLimits: { maxOldGenerationSizeMb: 512 } }); worker.on('message', (result) => this.handleResult(result)); worker.on('error', (err) => this.handleError(err, i)); this.workerPool.push(worker); } // Queue management with backpressure for (const file of files) { if (this.getAvailableWorkers() === 0) { await this.waitForWorker(); } this.assignTask(file); } } // Implement circuit breaker pattern async assignTask(file) { const worker = this.getAvailableWorker(); try { await Promise.race([ new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 30000) ), worker.postMessage(file) ]); } catch (err) { this.retryWithExponentialBackoff(file); } } }
Worker Implementation:
// pdf-worker.js const { workerData, parentPort } = require('worker_threads'); const { parsePDF } = require('./pdf-parser'); parentPort.on('message', async (file) => { try { // Use streams for large files const result = await parsePDF(file); parentPort.postMessage({ success: true, workerId: workerData.id, result }); } catch (err) { parentPort.postMessage({ success: false, error: err.message }); } });
Key Considerations:
Use streams for memory efficiency
Implement dead letter queue for failed jobs
Add health checks to worker processes
Consider horizontal scaling with message queues (Redis/Kafka)
4. Microservices Communication
Question: "Design an inter-service communication layer for 15 microservices. Compare gRPC vs REST vs WebSockets for different scenarios."
Expected Answer:
Architecture Decision Matrix:
| Scenario | Protocol | Reason | Implementation |
|---|---|---|---|
| Internal service-to-service | gRPC/Protocol Buffers | Low latency, type safety, bi-directional streaming | |
| External APIs | REST/GraphQL | Browser compatibility, caching, discoverability | |
| Real-time updates | WebSocket/SSE | Persistent connections, push notifications | |
| Event-driven systems | Message Queue (Kafka/RabbitMQ) | Decoupling, fault tolerance, replayability |
Implementation with Circuit Breaker:
class ServiceCommunicator { constructor() { this.circuitBreakers = new Map(); this.serviceRegistry = new ServiceRegistry(); } async callService(serviceName, method, data, options = {}) { const breaker = this.getCircuitBreaker(serviceName); if (breaker.isOpen()) { // Fallback strategy return this.executeFallback(serviceName, data); } try { const result = await breaker.execute(async () => { const serviceUrl = await this.serviceRegistry.resolve(serviceName); // Protocol selection based on service contract const protocol = this.getProtocolForService(serviceName); switch (protocol) { case 'grpc': return this.callGRPC(serviceUrl, method, data); case 'rest': return this.callREST(serviceUrl, method, data); case 'ws': return this.callWebSocket(serviceUrl, method, data); } }); return result; } catch (err) { // Retry logic with exponential backoff if (this.shouldRetry(err)) { return this.retryWithBackoff(serviceName, method, data); } throw err; } } // gRPC implementation with connection pooling async callGRPC(endpoint, method, data) { const pool = this.getGRPCPool(endpoint); const client = await pool.acquire(); try { const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 5); return await new Promise((resolve, reject) => { client[method](data, { deadline }, (err, response) => { if (err) reject(err); else resolve(response); }); }); } finally { pool.release(client); } } }
Advanced Patterns:
Request deduplication: Cache identical requests
Bulkhead isolation: Separate connection pools per service
Distributed tracing: Inject correlation IDs for debugging
III. ADVANCED PERFORMANCE & OPTIMIZATION
5. Database Optimization at Scale
Question: "Your Node.js/PostgreSQL application slows down under 10,000 concurrent users. Describe your optimization strategy from query to connection management."
Expected Answer:
Multi-Layer Optimization Strategy:
1. Connection Pooling with PgBouncer:
// Instead of single client const pool = new Pool({ max: 20, // Match DB max_connections / worker_count idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, application_name: 'api-service' }); // With statement timeout await pool.query('SET statement_timeout = 1000');
2. Query Optimization:
// BAD: N+1 queries const users = await db.query('SELECT * FROM users LIMIT 100'); for (const user of users) { const orders = await db.query('SELECT * FROM orders WHERE user_id = $1', [user.id]); } // GOOD: Batch query with CTE or JOIN const result = await db.query(` WITH user_orders AS ( SELECT u.*, json_agg(o.*) as orders FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.id = ANY($1) GROUP BY u.id ) SELECT * FROM user_orders `, [userIds]);
3. Read Replicas & Caching Strategy:
class OptimizedDatabase { constructor() { this.readPool = new Pool({ /* read replica config */ }); this.writePool = new Pool({ /* primary config */ }); this.redis = new Redis({ enableAutoPipelining: true }); } async getWithCache(key, query, params, ttl = 300) { // Cache-aside pattern with stale-while-revalidate const cached = await this.redis.get(key); if (cached) { // Background refresh for hot keys if (this.isHotKey(key)) { setImmediate(() => this.refreshCache(key, query, params, ttl)); } return JSON.parse(cached); } const result = await this.readPool.query(query, params); await this.redis.setex(key, ttl, JSON.stringify(result.rows)); return result.rows; } // Connection Leak Detection monitorConnections() { setInterval(() => { const stats = this.writePool.totalCount - this.writePool.idleCount; if (stats > this.writePool.max * 0.8) { alert('Connection pool nearing capacity'); } }, 5000); } }
4. Advanced Indexing Strategy:
-- Partial indexes for frequent filters CREATE INDEX idx_active_users ON users(id) WHERE status = 'active'; -- Composite indexes with careful column order CREATE INDEX idx_user_activity ON users(last_login_date, status) WHERE last_login_date > NOW() - INTERVAL '30 days'; -- BRIN for time-series data CREATE INDEX idx_orders_created ON orders USING BRIN(created_at);
5. Query Performance Monitoring:
// PostgreSQL pg_stat_statements analysis const slowQueries = await db.query(` SELECT query, calls, total_exec_time, mean_exec_time, rows / calls as avg_rows FROM pg_stat_statements WHERE mean_exec_time > 100 -- >100ms ORDER BY mean_exec_time DESC LIMIT 10 `);
6. Real-Time System Design
Question: "Design a real-time dashboard showing live metrics from 50,000 IoT devices. Each device sends data every 30 seconds. Requirements: <100ms latency, 99.9% uptime."
Expected Answer:
Architecture Diagram:
[IoT Devices] → [Load Balancer] → [Ingestion Layer] → [Processing Pipeline] → [WebSocket Server] → [Dashboard]
↑ ↓ ↓ ↑
[Health Check] [Time-Series DB] [Alert System] [Redis Pub/Sub]Implementation:
1. Ingestion Layer with Protocol Buffers:
// protobuf/schema.proto syntax = "proto3"; message DeviceData { string device_id = 1; int64 timestamp = 2; map<string, float> metrics = 3; Status status = 4; } // ingestion-server.js const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); class IngestionServer { constructor() { this.acceptedDevices = new LRU({ max: 100000 }); this.rateLimiter = new TokenBucket({ tokensPerInterval: 1000, interval: 'second' }); } async streamData(call) { // Bi-directional streaming call.on('data', async (deviceData) => { // Validation and rate limiting if (!this.validateDevice(deviceData.device_id)) { call.write({ error: 'Unauthorized device' }); return; } if (!this.rateLimiter.tryRemoveTokens(1)) { // Batch for later processing await this.buffer.write(deviceData); return; } // Fast path: Write to Kafka for async processing await this.kafkaProducer.send({ topic: 'device-metrics', messages: [{ value: Buffer.from(deviceData.serializeBinary()) }] }); // Acknowledge receipt call.write({ received: true, timestamp: Date.now() }); }); } }
2. Processing Pipeline:
class StreamProcessor { constructor() { // Windowed aggregation this.windows = new Map(); // State management with Redis this.redis = new Redis.Cluster([ { host: 'redis-1', port: 6379 }, { host: 'redis-2', port: 6379 } ]); } async process(deviceData) { const windowKey = `${deviceData.device_id}:${this.getWindowKey()}`; // Increment window counter await this.redis.hincrbyfloat( windowKey, 'temperature', deviceData.metrics.temperature ); // Publish to relevant dashboard rooms const room = `device:${deviceData.device_id}`; this.pubsub.publish(room, JSON.stringify({ type: 'update', data: deviceData.metrics, timestamp: Date.now() })); // Check alerts await this.checkAlerts(deviceData); } async checkAlerts(data) { // Complex event processing const rules = await this.getAlertRules(data.device_id); for (const rule of rules) { if (this.evaluateRule(rule, data)) { // Debounce alerts const alertKey = `alert:${data.device_id}:${rule.id}`; const lastAlert = await this.redis.get(alertKey); if (!lastAlert || Date.now() - lastAlert > rule.cooldown) { await this.sendAlert(rule, data); await this.redis.setex(alertKey, rule.cooldown, Date.now()); } } } } }
3. WebSocket Server with Room Management:
class DashboardServer { constructor() { this.wss = new WebSocket.Server({ noServer: true }); this.rooms = new Map(); // device_id → Set<WebSocket> // Shared Redis connection for horizontal scaling this.subscriber = redis.duplicate(); this.subscriber.on('message', (channel, message) => { this.broadcastToRoom(channel, message); }); } handleConnection(ws, request) { // Authentication const token = request.url.split('token=')[1]; const { deviceIds } = this.verifyToken(token); // Join rooms for subscribed devices deviceIds.forEach(deviceId => { this.joinRoom(ws, `device:${deviceId}`); this.subscriber.subscribe(`device:${deviceId}`); }); // Heartbeat const heartbeat = setInterval(() => { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; ws.ping(); }, 30000); ws.on('pong', () => { ws.isAlive = true; }); // Cleanup ws.on('close', () => { clearInterval(heartbeat); deviceIds.forEach(deviceId => { this.leaveRoom(ws, `device:${deviceId}`); }); }); } broadcastToRoom(room, message) { const clients = this.rooms.get(room); if (!clients) return; clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(message, { binary: false }); } }); } }
4. Dashboard Optimization:
// Frontend with efficient updates class DashboardClient { constructor() { this.ws = new WebSocket('wss://dashboard.example.com'); this.metrics = new Map(); this.updateQueue = []; this.isUpdating = false; // Batch updates for smooth rendering this.scheduleUpdate = this.debounce(this.updateUI, 16); // ~60fps } handleMessage(data) { this.updateQueue.push(data); if (!this.isUpdating) { this.isUpdating = true; requestAnimationFrame(() => this.processQueue()); } } processQueue() { // Process max 100 updates per frame const updates = this.updateQueue.splice(0, 100); updates.forEach(update => { this.metrics.set(update.deviceId, { ...this.metrics.get(update.deviceId), ...update.metrics, lastUpdate: Date.now() }); }); this.scheduleUpdate(); if (this.updateQueue.length > 0) { requestAnimationFrame(() => this.processQueue()); } else { this.isUpdating = false; } } }
Scalability Considerations:
Sharding: Device IDs → WebSocket server mapping
Backpressure: Kafka for buffering during spikes
Graceful degradation: Serve historical data when real-time fails
Monitoring: Prometheus metrics for message latency
IV. SECURITY & PRODUCTION READINESS
7. Security Hardening
Question: "Outline a comprehensive security strategy for a financial Node.js API. Include preventative measures, monitoring, and incident response."
Expected Answer:
Defense-in-Depth Strategy:
1. Input Validation & Sanitization:
const Joi = require('joi'); const sanitizeHtml = require('sanitize-html'); const { RateLimiterRedis } = require('rate-limiter-flexible'); class SecureAPI { constructor() { // Schema validation this.transactionSchema = Joi.object({ amount: Joi.number().positive().max(1000000).precision(2).required(), account: Joi.string().regex(/^[A-Z]{2}[0-9]{18}$/).required(), description: Joi.string().custom((value) => { return sanitizeHtml(value, { allowedTags: [], allowedAttributes: {} }); }) }); // Rate limiting by IP and user this.ipLimiter = new RateLimiterRedis({ storeClient: redis, keyPrefix: 'rl_ip', points: 100, // 100 requests duration: 15 * 60, // per 15 minutes blockDuration: 60 * 60 // block for 1 hour }); } async processTransaction(req, res) { // Validate input const { error, value } = this.transactionSchema.validate(req.body); if (error) throw new ValidationError(error.details); // Rate limit check try { await this.ipLimiter.consume(req.ip); await this.userLimiter.consume(req.user.id); } catch (rlRejected) { // Log potential attack await this.securityLog('RATE_LIMIT_EXCEEDED', req); throw new TooManyRequestsError(); } // Business logic with audit trail const auditId = await this.createAuditTrail(req); const result = await this.executeTransaction(value, auditId); return result; } }
2. Advanced Security Middleware Stack:
const helmet = require('helmet'); const hpp = require('hpp'); const cors = require('cors'); app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "validator.swagger.io"], fontSrc: ["'self'"] } }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true } })); app.use(hpp()); // HTTP Parameter Pollution app.use(cors({ origin: [/\.example\.com$/], // Regex allowlist credentials: true, maxAge: 86400 })); // Security headers app.use((req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); next(); });
3. Secrets Management:
// Never store secrets in code const { SecretManagerServiceClient } = require('@google-cloud/secret-manager'); class SecretManager { constructor() { this.cache = new Map(); this.client = new SecretManagerServiceClient(); } async getSecret(name, version = 'latest') { const cacheKey = `${name}:${version}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const [accessResponse] = await this.client.accessSecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/${version}` }); const secret = accessResponse.payload.data.toString(); // Cache with TTL this.cache.set(cacheKey, secret); setTimeout(() => this.cache.delete(cacheKey), 300000); // 5 minutes return secret; } } // Usage const dbPassword = await secretManager.getSecret('db-password-prod');
4. Real-time Threat Detection:
class ThreatDetector { constructor() { this.suspiciousPatterns = [ /(\b)(SELECT|INSERT|UPDATE|DELETE|DROP|UNION)(\b)/gi, /\.\.\//g, // Directory traversal /<script|javascript:/gi, /(\d{1,3}\.){3}\d{1,3}/ // Possible IP leak ]; this.behaviorBaseline = new Map(); } analyzeRequest(req) { const score = { sqlInjection: this.checkSQLi(req), xss: this.checkXSS(req), traversal: this.checkPathTraversal(req), behavioral: this.checkBehavior(req) }; const totalScore = Object.values(score).reduce((a, b) => a + b, 0); if (totalScore > 10) { this.blockIP(req.ip); this.alertSecurityTeam(req, score); return false; } return true; } checkBehavior(req) { const key = req.user?.id || req.ip; const now = Date.now(); if (!this.behaviorBaseline.has(key)) { this.behaviorBaseline.set(key, { requestCount: 1, lastRequest: now, endpoints: new Set([req.path]) }); return 0; } const behavior = this.behaviorBaseline.get(key); const timeDiff = now - behavior.lastRequest; // Abnormal request rate if (timeDiff < 100 && behavior.requestCount > 5) { return 5; } // Accessing unusual endpoints if (!behavior.endpoints.has(req.path) && behavior.endpoints.size > 3) { return 3; } behavior.requestCount++; behavior.lastRequest = now; behavior.endpoints.add(req.path); return 0; } }
5. Incident Response Pipeline:
class IncidentResponse { constructor() { this.siemClient = new SIEMClient(); this.slackClient = new WebClient(process.env.SLACK_TOKEN); } async handleIncident(type, data) { // Automatic containment await this.containThreat(data); // Notify team await this.slackClient.chat.postMessage({ channel: '#security-incidents', blocks: this.createIncidentMessage(type, data) }); // Preserve evidence const evidenceId = await this.preserveEvidence(data); // Create investigation ticket await this.createJiraTicket(type, data, evidenceId); // Execute response playbook await this.executePlaybook(type, data); } async containThreat(data) { // Block malicious IPs if (data.ip) { await this.iptables.blockIP(data.ip); await this.cloudflare.blockIP(data.ip); } // Revoke compromised tokens if (data.userId) { await this.revokeUserSessions(data.userId); } // Isolate affected systems if (data.service) { await this.isolateService(data.service); } } }
Compliance Considerations:
PCI DSS: Encrypt card data, regular vulnerability scans
GDPR: Data minimization, right to erasure implementation
SOC 2: Access logs, change management, penetration testing
V. BEHAVIORAL & SYSTEM DESIGN
8. System Design: URL Shortening Service
Question: "Design a URL shortener like Bitly handling 100M URLs daily. Discuss database schema, caching, scaling, and unique ID generation."
Expected Answer:
System Specifications:
QPS: 100M daily = ~1,160 req/s average, 5,000 req/s peak
Read:Write ratio: 90:10 (mostly reads)
Storage: 100M * 500 bytes = 50GB/year
Latency: <100ms for redirects
Architecture:
Client → Load Balancer → API Servers → Cache → DB
↑ ↓
Analytics ← Click Stream1. ID Generation (Base62 Encoding):
class IDGenerator { constructor() { this.ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; this.BASE = this.ALPHABET.length; this.redis = new Redis(); this.counterKey = 'url:id:counter'; } async generateId() { // Distributed counter using Redis const counter = await this.redis.incr(this.counterKey); // Encode to Base62 let num = counter; let encoded = ''; while (num > 0) { encoded = this.ALPHABET.charAt(num % this.BASE) + encoded; num = Math.floor(num / this.BASE); } // Pad to minimum length return encoded.padStart(6, '0'); } // Alternative: Snowflake-like IDs for distributed systems generateSnowflakeId() { const timestamp = Date.now() - 1609459200000; // Custom epoch const workerId = process.env.WORKER_ID || 0; const sequence = this.getSequence(); return ( (timestamp << 22) | (workerId << 12) | sequence ).toString(36); } }
2. Database Schema (PostgreSQL + TimeScaleDB):
-- URLs table CREATE TABLE urls ( id BIGSERIAL PRIMARY KEY, short_code VARCHAR(10) UNIQUE NOT NULL, original_url TEXT NOT NULL, user_id INTEGER REFERENCES users(id), created_at TIMESTAMPTZ DEFAULT NOW(), expires_at TIMESTAMPTZ, is_active BOOLEAN DEFAULT TRUE, metadata JSONB ); -- Indexes CREATE INDEX idx_short_code ON urls(short_code) WHERE is_active = TRUE; CREATE INDEX idx_user_urls ON urls(user_id, created_at DESC); -- Click analytics (time-series) CREATE TABLE clicks ( time TIMESTAMPTZ NOT NULL, short_code VARCHAR(10) NOT NULL, referrer TEXT, user_agent TEXT, ip_address INET, country CHAR(2), device_type VARCHAR(20) ); -- Convert to hypertable for partitioning SELECT create_hypertable('clicks', 'time');
3. Caching Strategy with Redis:
class URLCache { constructor() { this.redis = new Redis.Cluster([ { host: 'redis-1', port: 6379 }, { host: 'redis-2', port: 6379 } ]); // Local LRU cache for hot URLs this.localCache = new LRU({ max: 10000, ttl: 60000 // 1 minute }); } async get(shortCode) { // Check local cache first const local = this.localCache.get(shortCode); if (local) { this.recordHit('local'); return local; } // Check Redis const cached = await this.redis.get(`url:${shortCode}`); if (cached) { const url = JSON.parse(cached); this.localCache.set(shortCode, url); this.recordHit('redis'); return url; } // Cache miss this.recordMiss(); return null; } async set(shortCode, urlData) { // Set with different TTLs based on popularity const ttl = this.calculateTTL(urlData); await this.redis.setex( `url:${shortCode}`, ttl, JSON.stringify(urlData) ); // Pre-warm local cache for very popular URLs if (urlData.click_count > 1000) { this.localCache.set(shortCode, urlData); } } calculateTTL(urlData) { // Longer TTL for popular URLs if (urlData.click_count > 10000) return 86400; // 1 day if (urlData.click_count > 1000) return 3600; // 1 hour return 300; // 5 minutes default } }
4. Redirect Service with Analytics:
class RedirectService { constructor() { this.cache = new URLCache(); this.kafka = new KafkaProducer(); this.rateLimiter = new RateLimiterRedis({ keyPrefix: 'redirect', points: 100, // 100 redirects duration: 60 // per minute per IP }); } async redirect(req, res) { const shortCode = req.params.code; const ip = req.ip; // Rate limiting try { await this.rateLimiter.consume(ip); } catch { return res.status(429).send('Too Many Requests'); } // Get URL from cache/db const urlData = await this.getUrlData(shortCode); if (!urlData || !urlData.is_active) { return res.status(404).send('Not Found'); } // Check expiration if (urlData.expires_at && new Date(urlData.expires_at) < new Date()) { return res.status(410).send('Gone'); } // Async analytics (don't block redirect) setImmediate(() => { this.trackClick(shortCode, { ip, referrer: req.get('Referrer'), userAgent: req.get('User-Agent'), timestamp: new Date() }); }); // Update cache with incremented count urlData.click_count = (urlData.click_count || 0) + 1; this.cache.set(shortCode, urlData); // 301 Permanent redirect for SEO return res.redirect(301, urlData.original_url); } async trackClick(shortCode, data) { // Send to Kafka for async processing await this.kafka.send({ topic: 'clicks', messages: [{ value: JSON.stringify({ ...data, short_code: shortCode }) }] }); } }
5. Analytics Pipeline:
class AnalyticsProcessor { constructor() { this.clickhouse = new ClickHouseClient(); this.aggregates = new Map(); } async processClicks(batch) { // Real-time aggregation for (const click of batch) { const key = `${click.short_code}:${this.getTimeBucket(click.timestamp)}`; this.aggregates.set(key, { clicks: (this.aggregates.get(key)?.clicks || 0) + 1, unique_ips: this.updateUniqueIps(key, click.ip) }); } // Flush aggregates every minute if (Date.now() - this.lastFlush > 60000) { await this.flushAggregates(); } // Batch insert to ClickHouse await this.clickhouse.insert('clicks', batch); } getTimeBucket(timestamp) { // Round to nearest 5 minutes return new Date( Math.floor(timestamp.getTime() / 300000) * 300000 ); } }
Scaling Strategy:
Sharding: By short code prefix (consistent hashing)
CDN: Cache redirects at edge (Cloudflare Workers)
Database: Read replicas for analytics, connection pooling
Monitoring: RED metrics (Rate, Errors, Duration)
VI. DEVOPS & DEPLOYMENT STRATEGIES
9. Kubernetes Deployment & Management
Question: "Design a Kubernetes deployment for a Node.js microservice with zero-downtime deployments, proper resource management, and observability."
Expected Answer:
Complete Deployment Manifest:
# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-service namespace: production labels: app: api version: v1.2.0 spec: replicas: 3 revisionHistoryLimit: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 # Zero-downtime selector: matchLabels: app: api template: metadata: labels: app: api version: v1.2.0 annotations: prometheus.io/scrape: "true" prometheus.io/port: "3000" prometheus.io/path: "/metrics" spec: containers: - name: api image: gcr.io/project/api:v1.2.0 imagePullPolicy: IfNotPresent ports: - containerPort: 3000 name: http env: - name: NODE_ENV value: production - name: INSTANCE_ID valueFrom: fieldRef: fieldPath: metadata.name envFrom: - secretRef: name: api-secrets - configMapRef: name: api-config resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /health/live port: 3000 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /health/ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 1 startupProbe: httpGet: path: /health/startup port: 3000 initialDelaySeconds: 0 periodSeconds: 5 failureThreshold: 30 # Allow up to 150s for startup lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 30"] # Graceful shutdown securityContext: runAsNonRoot: true runAsUser: 1000 readOnlyRootFilesystem: true capabilities: drop: - ALL volumeMounts: - name: tmp-volume mountPath: /tmp volumes: - name: tmp-volume emptyDir: sizeLimit: 100Mi nodeSelector: node-type: api-optimized tolerations: - key: "dedicated" operator: "Equal" value: "api" effect: "NoSchedule" affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - api topologyKey: kubernetes.io/hostname --- # service.yaml apiVersion: v1 kind: Service metadata: name: api-service namespace: production spec: selector: app: api ports: - port: 80 targetPort: 3000 name: http type: ClusterIP --- # hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: api-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-service minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 - type: Pods pods: metric: name: http_requests_per_second target: type: AverageValue averageValue: 500 behavior: scaleUp: stabilizationWindowSeconds: 60 policies: - type: Percent value: 50 periodSeconds: 60 - type: Pods value: 5 periodSeconds: 60 selectPolicy: Max scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 300 --- # pdb.yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: api-pdb namespace: production spec: minAvailable: "50%" selector: matchLabels: app: api
Node.js Application with Kubernetes Integration:
// server.js with proper shutdown const http = require('http'); const { createTerminus } = require('@godaddy/terminus'); class K8sReadyApp { constructor() { this.server = null; this.isShuttingDown = false; this.connections = new Set(); } async start() { const app = express(); // Health endpoints app.get('/health/live', (req, res) => { res.json({ status: 'UP' }); }); app.get('/health/ready', async (req, res) => { if (this.isShuttingDown) { return res.status(503).json({ status: 'SHUTTING_DOWN' }); } // Check dependencies const checks = await this.runHealthChecks(); if (checks.healthy) { res.json({ status: 'UP', checks }); } else { res.status(503).json({ status: 'DOWN', checks }); } }); app.get('/health/startup', async (req, res) => { // Startup checks (DB connections, cache, etc.) const startupChecks = await this.runStartupChecks(); res.json(startupChecks); }); // Metrics endpoint app.get('/metrics', async (req, res) => { const metrics = await this.collectMetrics(); res.set('Content-Type', 'text/plain'); res.send(metrics); }); // Graceful shutdown this.server = http.createServer(app); // Track connections this.server.on('connection', (socket) => { this.connections.add(socket); socket.on('close', () => this.connections.delete(socket)); }); createTerminus(this.server, { signals: ['SIGTERM', 'SIGINT'], timeout: 30000, healthChecks: { '/health/live': () => ({ status: 'ok' }), '/health/ready': async () => { if (this.isShuttingDown) { throw new Error('Shutting down'); } return { status: 'ok' }; } }, onSignal: async () => { console.log('Starting graceful shutdown'); this.isShuttingDown = true; // Close all idle connections this.connections.forEach((socket) => { if (socket._idleTimeout) { socket.destroy(); } }); // Close database connections, etc. await this.closeResources(); }, onShutdown: async () => { console.log('Clean shutdown complete'); } }); this.server.listen(3000, () => { console.log('Server listening on port 3000'); this.registerWithServiceDiscovery(); }); } closeResources() { return Promise.all([ this.db.close(), this.redis.quit(), this.cache.close() ]); } }
Observability Stack:
// monitoring.js const prometheus = require('prom-client'); const { collectDefaultMetrics } = prometheus; class Monitoring { constructor() { // Collect default metrics collectDefaultMetrics({ timeout: 5000 }); // Custom metrics this.httpRequests = new prometheus.Counter({ name: 'http_requests_total', help: 'Total HTTP requests', labelNames: ['method', 'route', 'status'] }); this.responseTime = new prometheus.Histogram({ name: 'http_response_time_seconds', help: 'HTTP response time', labelNames: ['method', 'route'], buckets: [0.1, 0.5, 1, 2, 5] }); // Business metrics this.ordersProcessed = new prometheus.Counter({ name: 'orders_processed_total', help: 'Total orders processed' }); } middleware(req, res, next) { const start = Date.now(); const path = req.route?.path || req.path; // Track response res.on('finish', () => { const duration = Date.now() - start; this.httpRequests.inc({ method: req.method, route: path, status: res.statusCode }); this.responseTime.observe({ method: req.method, route: path }, duration / 1000); }); next(); } async collectMetrics() { return { nodejs: await this.getNodeMetrics(), system: await this.getSystemMetrics(), business: await this.getBusinessMetrics() }; } async getNodeMetrics() { const memory = process.memoryUsage(); return { heapUsed: memory.heapUsed, heapTotal: memory.heapTotal, rss: memory.rss, eventLoopLag: await this.getEventLoopLag(), activeHandles: process._getActiveHandles().length, activeRequests: process._getActiveRequests().length }; } }
CI/CD Pipeline (GitHub Actions):
# .github/workflows/deploy.yml name: Deploy to Kubernetes on: push: branches: [ main ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Use Node.js uses: actions/setup-node@v2 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: | npm test npm run test:integration npm run test:load -- --duration=60 - name: Security scan run: | npm audit npx snyk test - name: Build Docker image run: | docker build -t api:${{ github.sha }} . - name: Scan Docker image run: | docker scan api:${{ github.sha }} - name: Push to Registry if: github.event_name == 'push' run: | echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin docker tag api:${{ github.sha }} gcr.io/project/api:${{ github.sha }} docker push gcr.io/project/api:${{ github.sha }} deploy: needs: test if: github.event_name == 'push' runs-on: ubuntu-latest steps: - name: Configure Kubernetes uses: azure/setup-kubectl@v1 with: version: 'v1.24.0' - name: Deploy to Staging run: | kubectl config use-context staging kubectl set image deployment/api-service api=gcr.io/project/api:${{ github.sha }} -n staging kubectl rollout status deployment/api-service -n staging --timeout=300s - name: Run Integration Tests run: | npm run test:e2e -- --url=https://staging.api.example.com - name: Deploy to Production (Canary) if: success() run: | kubectl config use-context production # Create canary deployment kubectl apply -f canary-deployment.yaml # Monitor canary metrics ./scripts/monitor-canary.sh # If successful, roll out to all pods kubectl set image deployment/api-service api=gcr.io/project/api:${{ github.sha }} -n production kubectl rollout status deployment/api-service -n production --timeout=600s # Clean up canary kubectl delete deployment api-canary -n production - name: Rollback on Failure if: failure() run: | kubectl rollout undo deployment/api-service -n production kubectl rollout status deployment/api-service -n production
Advanced Features:
Feature flags: LaunchDarkly integration for gradual rollouts
Chaos engineering: Gremlin/Chaos Mesh for resilience testing
Cost optimization: Vertical Pod Autoscaler + node auto-scaling
Security: Pod Security Policies, network policies, OPA/Gatekeeper
VII. EMERGING TRENDS (2026)
10. Edge Computing with Node.js
Question: "How would you design a compute-at-edge system using Cloudflare Workers or similar? What Node.js patterns need adjustment for edge runtime constraints?"
Expected Answer:
Edge Architecture Pattern:
// Cloudflare Worker with Durable Objects export class UserSession { constructor(state, env) { this.state = state; this.storage = state.storage; this.env = env; } async fetch(request) { const url = new URL(request.url); switch (url.pathname) { case '/session': return this.handleSession(request); case '/preferences': return this.handlePreferences(request); default: return new Response('Not found', { status: 404 }); } } async handleSession(request) { // In-memory state at edge let session = await this.storage.get('session'); if (!session) { session = { id: this.state.id.toString(), createdAt: Date.now(), data: {} }; await this.storage.put('session', session); } // Update with request data const data = await request.json(); session.data = { ...session.data, ...data }; session.updatedAt = Date.now(); // Sync to central DB eventually this.syncToDatabase(session); return new Response(JSON.stringify(session), { headers: { 'Content-Type': 'application/json' } }); } async syncToDatabase(session) { // Background sync using queue await this.env.QUEUE.send({ type: 'session_sync', session, timestamp: Date.now() }); } } // Edge middleware chain const edgeMiddleware = [ // Geolocation-based routing async (context, next) => { const country = context.request.cf?.country; if (country === 'RU') { // Route to different origin context.origin = 'https://ru-origin.example.com'; } return next(); }, // A/B testing at edge async (context, next) => { const cookie = context.request.headers.get('Cookie'); const userId = getUserIdFromCookie(cookie); // Deterministic variant based on user ID const variant = hash(userId) % 100 < 50 ? 'A' : 'B'; context.variant = variant; // Inject variant into request context.request.headers.set('X-Variant', variant); return next(); }, // Edge caching with stale-while-revalidate async (context, next) => { const cacheKey = context.request.url; const cache = caches.default; let response = await cache.match(cacheKey); if (!response) { response = await next(); // Cache for 5 minutes, stale for 1 hour response.headers.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=3600' ); context.waitUntil(cache.put(cacheKey, response.clone())); } return response; } ]; // Main worker handler export default { async fetch(request, env, ctx) { const context = { request, env, ctx }; // Execute middleware chain const handler = compose(edgeMiddleware); return handler(context, () => { // Final handler return fetch(request); }); } };
Adjustments for Edge Runtime:
Stateless by default: Use Durable Objects for state
Small bundle size: <1MB worker scripts
Global variables: Limited APIs, no Node.js modules
Cold starts: Keep initialization <50ms
Data locality: Cache data closest to users
Hybrid Edge-Cloud Architecture:
class HybridSystem { constructor() { this.edgeWorkers = new EdgeWorkerPool(); this.cloudFunctions = new CloudFunctionClient(); } async processRequest(request) { const userLocation = request.headers.get('CF-IPCountry'); const path = new URL(request.url).pathname; // Routing logic if (this.shouldProcessAtEdge(path, userLocation)) { // Process at edge return this.edgeWorkers.process(request); } else { // Forward to cloud const enrichedRequest = await this.enrichAtEdge(request); return this.cloudFunctions.process(enrichedRequest); } } shouldProcessAtEdge(path, country) { const edgePaths = ['/api/session', '/api/geo', '/static/']; const edgeCountries = ['US', 'EU', 'JP']; return edgePaths.some(p => path.startsWith(p)) && edgeCountries.includes(country); } async enrichAtEdge(request) { // Add edge-computed data const geoData = this.getGeoData(request); const userAgent = this.parseUA(request.headers.get('User-Agent')); return new Request(request, { headers: { ...Object.fromEntries(request.headers), 'X-Edge-Data': JSON.stringify({ geoData, userAgent }) } }); } }
CONCLUSION
This comprehensive guide covers the essential Node.js architecture interview topics for 2026. Master these concepts through:
Practical implementation: Build sample projects for each pattern
Performance testing: Load test your solutions to understand limits
Debugging practice: Use diagnostic tools on real applications
System design: Whiteboard multiple solutions to the same problem
Stay updated: Follow Node.js releases, RFCs, and emerging patterns