Tekion Corp company logo - healthcare software solutions provider

Real Tekion Corp Interview Experience

Event Pub-Sub, API Concurrency, System Design & Architecture

4
Rounds
40-60
Minutes Each
70-90
LPA Package
βœ…
Selected

Interview Structure

Rounds Breakdown

  • Round 1: In-person coding (Event Emitter) - 60 min
  • Round 2: Remote (API Concurrency & Retries) - 40 min
  • Round 3/4 - Team 1: System Design + Principle Architect
  • Round 3/4 - Team 2: System Design + Hiring Manager

Round 1: Event Emitter Implementation (60 minutes)

In-person | JavaScript | Only Round Conducted

πŸ“ Question

Build a system where you're creating an event publisher-subscriber similar to Node.js EventEmitter. This should support:
  • Adding event listeners with on(eventName, callback)
  • Emitting events with emit(eventName, ...args)
  • One-time listeners with once(eventName, callback)
  • Removing listeners with off(eventName, callback)
  • Getting listener count with listenerCount(eventName)

πŸ’‘ Approach

Use an object to store event names as keys and arrays of callbacks as values. Handle both regular and one-time listeners by wrapping callbacks. This demonstrates understanding of the Observer pattern and event-driven architecture.

βœ… Solution

class EventEmitter {
  constructor() {
    this.events = {};
  }

  // Add event listener
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push({
      callback,
      once: false
    });
    return this; // for chaining
  }

  // Add one-time listener
  once(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push({
      callback,
      once: true
    });
    return this;
  }

  // Emit event
  emit(eventName, ...args) {
    if (!this.events[eventName]) return false;

    const listeners = this.events[eventName];
    const toRemove = [];

    listeners.forEach((listener, index) => {
      listener.callback(...args);
      if (listener.once) {
        toRemove.push(index);
      }
    });

    // Remove once listeners in reverse order
    for (let i = toRemove.length - 1; i >= 0; i--) {
      listeners.splice(toRemove[i], 1);
    }

    return listeners.length > 0;
  }

  // Remove specific listener
  off(eventName, callback) {
    if (!this.events[eventName]) return this;

    this.events[eventName] = this.events[eventName].filter(
      listener => listener.callback !== callback
    );

    return this;
  }

  // Get listener count
  listenerCount(eventName) {
    return this.events[eventName]?.length || 0;
  }

  // Remove all listeners
  removeAllListeners(eventName) {
    if (eventName) {
      delete this.events[eventName];
    } else {
      this.events = {};
    }
    return this;
  }
}

// Usage Example
const emitter = new EventEmitter();

emitter.on('message', (data) => {
  console.log('Listener 1:', data);
});

emitter.once('message', (data) => {
  console.log('One-time listener:', data);
});

emitter.emit('message', 'Hello World'); // Both listeners fire
emitter.emit('message', 'Hello Again'); // Only listener 1 fires
console.log(emitter.listenerCount('message')); // 1

🎯 Key Points Evaluators Look For

  • Data Structure: Using object with arrays for storing listeners
  • Callback Management: Proper handling of multiple listeners
  • Once Listeners: Correctly removing one-time listeners after emission
  • Method Chaining: Returning `this` for fluent API
  • Edge Cases: Handling non-existent events, empty listener lists
  • Design Pattern: Recognition of Observer pattern implementation

Round 1 Takeaways

βœ… What Went Well

  • Clean implementation of Observer pattern
  • Proper handling of one-time listeners
  • Good understanding of event-driven architecture
  • Method chaining for fluent API

πŸ“š Key Learnings

  • Event emitters are fundamental to Node.js
  • Observer pattern applies to real-world scenarios
  • Proper memory management with event listeners
  • Edge cases matter in interviews

Event-driven systems are everywhere in modern applications. Understanding pub-sub patterns separates engineers who code features from those who architect systems. Master system design with us β†’

Round 2: API Concurrency & Retries (40 minutes)

Remote Interview | Advanced JavaScript | Only Round Conducted

πŸ“ Question

You have an array of promise-returning functions. Some should execute concurrently and some sequentially. Which ones are concurrent vs sequential is specified as an argument.
  • Final result should come only after all promises are resolved
  • Maintain order of results as per the input array
  • Handle errors gracefully

πŸ”„ Follow-up Question

Some promises may fail. Add retry logic:
  • Retry failed promises up to 3 times
  • Respect the concurrent/sequential execution pattern even during retries
  • Return all results (successful or failed) in the same order

πŸ’‘ Approach

Create a function that accepts an array of functions and an array of execution types (concurrent/sequential). For concurrent promises, use Promise.all(). For sequential, use a loop with await. Implement retry logic that respects these execution patterns.

βœ… Solution - Mixed Concurrent/Sequential with Retries

/**
 * Execute promises with mixed concurrent/sequential execution
 * @param {Array} promiseFuncs - Array of promise-returning functions
 * @param {Array<'concurrent'|'sequential'>} executionPattern - Execution type for each function
 * @param {number} maxRetries - Max retry attempts for failed promises (default: 3)
 * @returns {Promise} Results in the same order as input
 */
async function executeMixedPromises(promiseFuncs, executionPattern, maxRetries = 3) {
  const results = new Array(promiseFuncs.length);
  const errors = new Array(promiseFuncs.length);

  /**
   * Execute a single promise with retry logic
   */
  async function executeWithRetry(func, index) {
    let lastError;

    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        const result = await func();
        results[index] = result;
        errors[index] = null;
        return result;
      } catch (error) {
        lastError = error;
        console.log(`Attempt ${attempt + 1} failed for index ${index}: ${error.message}`);
        
        if (attempt < maxRetries) {
          // Exponential backoff: wait before retrying
          const delay = 1000 * Math.pow(2, attempt);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }

    // All retries exhausted
    errors[index] = lastError;
    results[index] = null;
    return null;
  }

  /**
   * Process concurrent group - all execute at the same time
   */
  async function processConcurrentGroup(indices) {
    const promises = indices.map(i => executeWithRetry(promiseFuncs[i], i));
    await Promise.all(promises);
  }

  /**
   * Process sequential group - one after another
   */
  async function processSequentialGroup(indices) {
    for (const i of indices) {
      await executeWithRetry(promiseFuncs[i], i);
    }
  }

  // Group indices by execution type
  const concurrentIndices = [];
  const sequentialIndices = [];

  executionPattern.forEach((type, index) => {
    if (type === 'concurrent') {
      concurrentIndices.push(index);
    } else {
      sequentialIndices.push(index);
    }
  });

  // Execute both groups (they run in parallel with each other)
  await Promise.all([
    processConcurrentGroup(concurrentIndices),
    processSequentialGroup(sequentialIndices)
  ]);

  return {
    results,
    errors,
    allSuccessful: errors.every(e => e === null),
    summary: {
      total: promiseFuncs.length,
      successful: results.filter(r => r !== null).length,
      failed: errors.filter(e => e !== null).length
    }
  };
}

// Usage Example
const api1 = () => fetch('/api/users').then(r => r.json());
const api2 = () => fetch('/api/posts').then(r => r.json());
const api3 = () => fetch('/api/comments').then(r => r.json());
const api4 = () => fetch('/api/likes').then(r => r.json());

async function example() {
  const result = await executeMixedPromises(
    [api1, api2, api3, api4],
    ['concurrent', 'concurrent', 'sequential', 'sequential'],
    3 // max retries
  );

  console.log('Results:', result.results);
  console.log('Errors:', result.errors);
  console.log('Summary:', result.summary);
  /*
  Output structure:
  {
    results: [usersData, postsData, commentsData, likesData],
    errors: [null, null, null, someError],
    allSuccessful: false,
    summary: { total: 4, successful: 3, failed: 1 }
  }
  */
}

// Another example: Custom error handling
async function exampleWithErrorDetails() {
  const funcs = [
    () => Promise.resolve('Success 1'),
    () => Promise.reject(new Error('API Down')),
    () => Promise.resolve('Success 3'),
    () => Promise.reject(new Error('Timeout'))
  ];

  const pattern = ['concurrent', 'concurrent', 'sequential', 'sequential'];
  const result = await executeMixedPromises(funcs, pattern, 3);

  // Process results
  result.results.forEach((res, i) => {
    if (result.errors[i]) {
      console.error(`Index ${i} failed after 3 retries: ${result.errors[i].message}`);
    } else {
      console.log(`Index ${i} succeeded: ${res}`);
    }
  });
}

🎯 Key Points Evaluators Look For

  • Concurrent Execution: Using Promise.all() for parallel promises
  • Sequential Execution: Using await in loops for one-by-one execution
  • Mixed Execution: Both patterns running in parallel with each other
  • Retry Logic: Exponential backoff respecting execution patterns
  • Result Ordering: Maintaining order of results as per input array
  • Error Handling: Capturing all errors without blocking other executions
  • Summary Info: Providing useful debugging information

Round 2 Takeaways

βœ… What Went Well

  • Clear understanding of async/await patterns
  • Correct implementation of mixed execution models
  • Robust retry logic with exponential backoff
  • Proper error handling without blocking

πŸ“š Key Learnings

  • Concurrent vs sequential execution trade-offs
  • Importance of result ordering in APIs
  • Resilience patterns for production systems
  • Debugging concurrent failures is complex

Building resilient APIs isn't just about making callsβ€”it's about handling failures gracefully. This knowledge separates junior developers from senior engineers. Learn production-grade patterns β†’

Round 3/4 - Team 1: System Design + Principle Architect (Combined)

System Design Deep Dive + Architectural Decision Making | First Attempt

πŸ“ Questions

Part 1: Design a Real-time Chat System
  • How would you design a real-time chat system for 1M+ concurrent users?
  • What are the key components and their responsibilities?
  • How to handle message ordering and delivery guarantees?
  • How to implement typing indicators?
  • How to handle offline messages?
Part 2: Implementation
  • Code a simplified version of the chat system
  • WebSocket implementation for real-time communication
  • Message queue/buffering system

πŸ’‘ Approach

Start with the architecture: WebSocket servers, message brokers (Redis), databases for persistence. Discuss scalability concerns, load balancing, and database sharding. Then code a simplified client-server chat implementation with message history.

βœ… System Architecture

/*
CHAT SYSTEM ARCHITECTURE

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Client Applications             β”‚
β”‚   (Web, Mobile, Desktop)                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
      WebSocket Connection
             β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   API Gateway / Load Balancer           β”‚
β”‚   (Distributes traffic)                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  WebSocket       β”‚  β”‚  WebSocket         β”‚
β”‚  Server 1        β”‚  β”‚  Server 2          β”‚
β”‚  (Connection     β”‚  β”‚  (Connection       β”‚
β”‚   Management)    β”‚  β”‚   Management)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                      β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  Message Broker/Queue   β”‚
        β”‚  (Redis/RabbitMQ)       β”‚
        β”‚  - Real-time messages   β”‚
        β”‚  - Pub/Sub              β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  Databases              β”‚
        β”‚  - User data (SQL)      β”‚
        β”‚  - Messages (NoSQL)     β”‚
        β”‚  - Presence (Cache)     β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
*/

βœ… Implementation Code

// Server-side: Chat System with WebSocket
const express = require('express');
const http = require('http');
const WebSocket = require('ws');

class ChatSystem {
  constructor() {
    this.app = express();
    this.server = http.createServer(this.app);
    this.wss = new WebSocket.Server({ server: this.server });
    
    // Store connected users
    this.users = new Map(); // userId -> {ws, status, typingIn}
    
    // Store messages
    this.messages = new Map(); // roomId -> [messages]
    
    // Typing indicators
    this.typingUsers = new Map(); // roomId -> Set of userIds
    
    this.setupWebSocket();
  }

  setupWebSocket() {
    this.wss.on('connection', (ws) => {
      let userId = null;

      ws.on('message', (data) => {
        const message = JSON.parse(data);

        switch (message.type) {
          case 'JOIN':
            this.handleJoin(ws, message, (id) => userId = id);
            break;

          case 'SEND_MESSAGE':
            this.handleSendMessage(userId, message);
            break;

          case 'TYPING':
            this.handleTyping(userId, message);
            break;

          case 'DISCONNECT':
            this.handleDisconnect(userId);
            break;
        }
      });

      ws.on('close', () => {
        if (userId) this.handleDisconnect(userId);
      });
    });
  }

  handleJoin(ws, message, setUserId) {
    const { userId, roomId } = message;
    
    setUserId(userId);
    this.users.set(userId, {
      ws,
      status: 'online',
      roomId
    });

    // Send previous messages (message history)
    if (!this.messages.has(roomId)) {
      this.messages.set(roomId, []);
    }
    
    ws.send(JSON.stringify({
      type: 'HISTORY',
      messages: this.messages.get(roomId)
    }));

    // Broadcast user joined
    this.broadcastToRoom(roomId, {
      type: 'USER_JOINED',
      userId,
      onlineCount: this.getOnlineUsersInRoom(roomId)
    });
  }

  handleSendMessage(userId, message) {
    const { roomId, content } = message;
    const timestamp = Date.now();

    const chatMessage = {
      id: `msg_${timestamp}_${Math.random()}`,
      userId,
      content,
      timestamp,
      status: 'delivered'
    };

    // Store message
    this.messages.get(roomId).push(chatMessage);

    // Broadcast to room
    this.broadcastToRoom(roomId, {
      type: 'MESSAGE',
      message: chatMessage
    });

    // Clear typing indicator
    this.handleTypingStop(userId, roomId);
  }

  handleTyping(userId, message) {
    const { roomId } = message;
    
    if (!this.typingUsers.has(roomId)) {
      this.typingUsers.set(roomId, new Set());
    }

    this.typingUsers.get(roomId).add(userId);

    // Broadcast typing indicator
    this.broadcastToRoom(roomId, {
      type: 'USER_TYPING',
      userId,
      typingUsers: Array.from(this.typingUsers.get(roomId))
    });

    // Auto-clear after 3 seconds if no new message
    setTimeout(() => {
      this.handleTypingStop(userId, roomId);
    }, 3000);
  }

  handleTypingStop(userId, roomId) {
    if (this.typingUsers.has(roomId)) {
      this.typingUsers.get(roomId).delete(userId);
      
      this.broadcastToRoom(roomId, {
        type: 'USER_TYPING',
        typingUsers: Array.from(this.typingUsers.get(roomId))
      });
    }
  }

  handleDisconnect(userId) {
    const user = this.users.get(userId);
    if (user) {
      const { roomId } = user;
      this.users.delete(userId);

      this.broadcastToRoom(roomId, {
        type: 'USER_LEFT',
        userId,
        onlineCount: this.getOnlineUsersInRoom(roomId)
      });
    }
  }

  broadcastToRoom(roomId, message) {
    this.users.forEach((user) => {
      if (user.roomId === roomId && user.ws.readyState === WebSocket.OPEN) {
        user.ws.send(JSON.stringify(message));
      }
    });
  }

  getOnlineUsersInRoom(roomId) {
    let count = 0;
    this.users.forEach((user) => {
      if (user.roomId === roomId) count++;
    });
    return count;
  }

  start(port = 3000) {
    this.server.listen(port, () => {
      console.log(`Chat server running on port ${port}`);
    });
  }
}

// Client-side: Chat UI
class ChatClient {
  constructor(userId, roomId) {
    this.userId = userId;
    this.roomId = roomId;
    this.ws = new WebSocket('ws://localhost:3000');
    this.messageQueue = []; // For offline messages

    this.setupConnection();
    this.setupUI();
  }

  setupConnection() {
    this.ws.onopen = () => {
      console.log('Connected');
      
      // Send join message
      this.ws.send(JSON.stringify({
        type: 'JOIN',
        userId: this.userId,
        roomId: this.roomId
      }));

      // Send queued messages
      this.messageQueue.forEach(msg => this.ws.send(msg));
      this.messageQueue = [];
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      this.handleMessage(message);
    };

    this.ws.onclose = () => {
      console.log('Disconnected - messages will be queued');
    };
  }

  sendMessage(content) {
    const message = JSON.stringify({
      type: 'SEND_MESSAGE',
      roomId: this.roomId,
      content
    });

    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(message);
    } else {
      // Queue for offline
      this.messageQueue.push(message);
    }
  }

  sendTyping() {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'TYPING',
        roomId: this.roomId
      }));
    }
  }

  handleMessage(message) {
    switch (message.type) {
      case 'HISTORY':
        this.displayHistory(message.messages);
        break;

      case 'MESSAGE':
        this.displayMessage(message.message);
        break;

      case 'USER_TYPING':
        this.updateTypingIndicator(message.typingUsers);
        break;

      case 'USER_JOINED':
        console.log(`User joined. Online: ${message.onlineCount}`);
        break;

      case 'USER_LEFT':
        console.log(`User left. Online: ${message.onlineCount}`);
        break;
    }
  }

  displayHistory(messages) {
    messages.forEach(msg => this.displayMessage(msg));
  }

  displayMessage(message) {
    const messageEl = document.createElement('div');
    messageEl.className = `message ${message.userId === this.userId ? 'sent' : 'received'}`;
    messageEl.innerHTML = `
      ${message.userId}: ${message.content}
      ${new Date(message.timestamp).toLocaleTimeString()}
    `;
    document.getElementById('messages').appendChild(messageEl);
  }

  updateTypingIndicator(typingUsers) {
    const indicator = document.getElementById('typing-indicator');
    if (typingUsers.length > 0) {
      indicator.textContent = `${typingUsers.join(', ')} is typing...`;
    } else {
      indicator.textContent = '';
    }
  }

  setupUI() {
    const input = document.getElementById('message-input');
    const sendBtn = document.getElementById('send-btn');

    input.addEventListener('input', () => this.sendTyping());
    
    sendBtn.addEventListener('click', () => {
      if (input.value.trim()) {
        this.sendMessage(input.value);
        input.value = '';
      }
    });
  }
}

// Start server
const chat = new ChatSystem();
chat.start();

🎯 Key Points Evaluators Look For

  • Architecture: WebSocket servers, message brokers, databases
  • Scalability: Load balancing, horizontal scaling considerations
  • Message Delivery: Ordering, deduplication, persistence
  • Real-time Features: Typing indicators, online status, presence
  • Offline Support: Message queue, retry mechanism
  • Data Consistency: Handling duplicates, message ordering
  • Performance: Caching, indexing, query optimization

Part A: Principle Architecture Questions

❓ Question 1: Efficient Caching Strategy

How do you handle efficient caching in a large-scale application?

πŸ’‘ Key Answer Points

/*
CACHING STRATEGY:

1. Cache Layers (from fastest to slowest):
   - Browser/Client Cache (localStorage, sessionStorage)
   - CDN Cache (for static assets)
   - Server Memory Cache (Redis, Memcached)
   - Database Query Cache
   - Disk Cache

2. Cache Invalidation Strategies:
   - TTL (Time-To-Live): Automatic expiration
   - Event-based: Invalidate on data change
   - LRU (Least Recently Used): Remove old entries
   - Manual invalidation: Explicit cache clear

3. Implementation Example:
*/

class CacheManager {
  constructor(maxSize = 100, ttl = 5 * 60 * 1000) {
    this.cache = new Map();
    this.ttl = ttl;
    this.maxSize = maxSize;
    this.accessOrder = []; // LRU tracking
  }

  set(key, value) {
    if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
      const oldestKey = this.accessOrder.shift();
      this.cache.delete(oldestKey);
    }
    this.cache.set(key, { value, timestamp: Date.now() });
    this.accessOrder.push(key);
  }

  get(key) {
    if (!this.cache.has(key)) return null;
    const entry = this.cache.get(key);
    if (Date.now() - entry.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }
    this.accessOrder = this.accessOrder.filter(k => k !== key);
    this.accessOrder.push(key);
    return entry.value;
  }

  invalidate(key) {
    this.cache.delete(key);
    this.accessOrder = this.accessOrder.filter(k => k !== key);
  }
}

❓ Question 2: Scaling Images & Videos

How would you cache and serve images and videos at scale globally?

πŸ’‘ Key Points

  • Use CDN (CloudFront, Cloudflare) for geographic distribution
  • Implement adaptive bitrate streaming for videos
  • Use Service Workers for offline caching
  • Progressive loading with thumbnails
  • Image optimization (WebP, compression, lazy loading)

❓ Question 3: Micro Frontend Architecture

How do you implement true micro frontends with independent deployments?

πŸ’‘ Implementation Approaches

  • Module Federation (Webpack 5) - shared dependencies at build time
  • Web Components - encapsulated, framework-agnostic
  • iFrame-based approach - complete isolation
  • Custom loaders - load apps dynamically
  • Shared context via custom events or message passing

Round 3/4 - Team 1 Takeaways

βœ… What Went Well

  • Strong architectural thinking and patterns
  • Deep understanding of caching strategies
  • Knowledge of micro-frontend approaches
  • Ability to discuss trade-offs clearly

πŸ“š Key Learnings

  • Principle architects focus on scalability
  • Caching is fundamental to performance
  • Multiple solutions to architectural problems
  • Communication of decisions matters

Architecture and principle engineering is what separates staff engineers from seniors. Mastering caching, scaling, and micro-architecture decisions is crucial. Master architecture patterns β†’

Round 3/4 - Team 2: System Design + Hiring Manager (Combined)

System Design Deep Dive + Behavioral Interview | Second Attempt (Success)

System Design: Real-time Chat Application

πŸ“ Challenge

Design a real-time chat system for 1M+ concurrent users with message ordering, delivery guarantees, typing indicators, offline support, and rich media sharing.

πŸ’‘ Key Architectural Components

WebSocket Servers for real-time communication β€’ Message Broker (Redis/RabbitMQ) for pub/sub β€’ NoSQL Database for message persistence β€’ Cache Layer for recent messages β€’ Load Balancer for horizontal scaling

Part B: Hiring Manager Round - Behavioral & Practical

❓ Question 1: Managing Junior Developers

Scenario: You're given 2 junior developers to manage. How do you train them effectively?

πŸ’‘ Sample Answer

Approach:
1. Structured Onboarding:
   - Pair programming for first week
   - Clear documentation and code walkthroughs
   - Small tasks to build confidence

2. Incremental Complexity:
   - Start with feature flags and isolated modules
   - Code reviews for every change
   - Design discussions before implementation

3. Continuous Learning:
   - Weekly 1-on-1 sync-ups
   - System design discussions
   - Mentoring on best practices
   - Encourage reading industry articles

4. Feedback Loop:
   - Real-time feedback on PRs
   - Celebrate wins and learnings
   - Help them understand failures

5. Growth Tracking:
   - Set clear goals (3, 6, 12 months)
   - Track skills improvement
   - Provide career guidance

❓ Question 2: Closure - Practical Example

Give a practical example of where you've used closures in production code.

πŸ’‘ Production Example

// Real-world closure: API request debouncing
function createDebouncedSearch(apiCall) {
  let timeoutId = null;
  let lastQuery = '';

  return function debounce(query) {
    lastQuery = query; // Closure captures this

    if (timeoutId) {
      clearTimeout(timeoutId);
    }

    timeoutId = setTimeout(() => {
      apiCall(lastQuery); // Uses captured lastQuery
    }, 300);
  };
}

// Usage: Search input field
const debouncedSearch = createDebouncedSearch((query) => {
  fetch(`/api/search?q=${query}`)
    .then(r => r.json())
    .then(results => {
      displayResults(results);
    });
});

document.getElementById('search').addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

/*
Why closure works here:
- timeoutId and lastQuery are "remembered"
- Each debounce call accesses the same variables
- Without closure, we'd need global variables (bad!)
- Clean, encapsulated solution
*/

❓ Question 3: Platform-Specific Components (iOS vs Android)

How do you ensure Android and iOS have different component implementations (e.g., different button styles)?

πŸ’‘ Code Solution

// Platform detection
const getPlatform = () => {
  const userAgent = navigator.userAgent;
  if (/iPad|iPhone|iPod/.test(userAgent)) return 'ios';
  if (/Android/.test(userAgent)) return 'android';
  return 'web';
};

// Platform-specific button component
const ButtonComponent = ({ label, onPress }) => {
  const platform = getPlatform();

  const styles = {
    ios: {
      padding: '12px 24px',
      borderRadius: '8px',
      backgroundColor: '#007AFF',
      color: 'white',
      fontSize: '16px',
      fontWeight: '600',
      border: 'none',
      cursor: 'pointer',
    },
    android: {
      padding: '8px 16px',
      borderRadius: '4px',
      backgroundColor: '#1976D2',
      color: 'white',
      fontSize: '14px',
      fontWeight: '500',
      border: 'none',
      cursor: 'pointer',
      textTransform: 'uppercase',
    },
    web: {
      padding: '10px 20px',
      borderRadius: '4px',
      backgroundColor: '#6200EE',
      color: 'white',
      fontSize: '14px',
      border: '1px solid #6200EE',
      cursor: 'pointer',
    },
  };

  return (
    
  );
};

// Alternative: Using React Native for true native components
import React from 'react';
import { Button, Platform } from 'react-native';

const NativeButton = ({ label, onPress }) => {
  // React Native uses native components automatically
  return (
    

❓ Question 4: Behavioral - Conflict Resolution

Scenario: You disagree with a senior engineer's architectural decision. How do you handle it?

πŸ’‘ Sample Answer

Approach:
1. Listen First:
   - Understand their reasoning fully
   - Ask clarifying questions
   - Look for valid points I might have missed

2. Present Your Perspective:
   - Focus on tradeoffs, not "right vs wrong"
   - Use data/examples when possible
   - "Have you considered...?" instead of "You're wrong"

3. Collaborate:
   - Suggest alternatives together
   - Compromise if both approaches have merit
   - Document decision rationale

4. Escalate if Needed:
   - If it's critical (security, performance)
   - Involve team leads/architects
   - Keep discussion professional

5. Execute Well:
   - Once decided, commit fully
   - Learn from both approaches
   - Don't say "I told you so" if issues arise

Example: I disagreed with a caching strategy.
Instead of "That won't work," I said:
"I'm concerned about cache invalidation complexity.
Could we benchmark both approaches on a subset?"
Result: We tested both, combined best of both.

Round 3/4 - Team 2 Takeaways

βœ… What Went Well

  • Strong system design fundamentals
  • Excellent behavioral answers and examples
  • Good conflict resolution approach
  • Real-world practical thinking

πŸ“š Key Learnings

  • HMs care about soft skills and collaboration
  • Real-world examples resonate better
  • Humble approach to disagreements matters
  • Execution and team dynamics are key

Architecture and leadership skills are what separate senior engineers from juniors. Understanding caching, micro frontends, and how to lead teams is what gets you 70-90 LPA roles. Master architecture and leadership β†’

πŸ’‘ Final Advice

The path to senior engineering roles (70-90 LPA): It's not just about passing interviewsβ€”it's about mastering the skills that matter at scale. Event-driven systems, resilient API design, and real-time architecture are patterns you'll use everywhere. But equally important is learning to lead, mentor, and make architectural decisions under constraints. The second team selected you because you showed you could do both.

Focus on understanding the "why" behind patterns, not just the "how."

Ready for Tekion Corp?

Master the skills that get you 70-90 LPA offers. Join our cohort for structured prep, real interview questions, and personalized feedback.

Join Next Cohort