Level: Staff-Level
Round: Onsite · Type: System Design · Difficulty: 7/10 · Duration: 60 min · Interviewer: Neutral
Topics: System Design, Chatbot, Event-Driven Architecture, Data Structures, Algorithms
Location: San Francisco Bay Area
Interview date: 2026-01-20
Question: Design a chat system that supports human users and automated bots with extensible functionality. Focus on designing the ChatApp class to manage channels and process messages from users and bots like AwayBot, TacoBot, and MeetBot.
Design a chat system supporting human users and automated bots with the ability to add new bot types without modifying existing logic.
ChatApp to manage channels and process messages.AwayBot: Records user's away status.
/away .AwayBot: <user> is away: <reason>.TacoBot: Gives tacos from one user to another.
/givetaco @<recipient> <count>.MeetBot: Sets up meetings and marks users as away.
/meet <targetUser>.MeetBot: Google Meet with @<sender>, and <targetUser> starting at /abc-def-123.@<user> may be in a meeting right now.My proposed solution uses an event-driven architecture to allow bots to react to messages. Here's my Python implementation:
` from typing import List, Dict, Callable, Any, Type from collections import defaultdict
class ChatApp: def init(self): self.messages: List[str] = [] self.eventBus = EventBus() self.channel = Channel(self.messages) self.registerAllBots(self.channel, self.eventBus)
def sendMessage(self, name: str, text: str):
self.messages.append(f"{name}: {text}")
self.eventBus.publish(UserMessageEvent(name, text))
def getMessages(self) -> List[str]:
return self.messages.copy()
def registerAllBots(self, ctx, bus):
AwayBot().init(ctx, bus)
MeetBot().init(ctx, bus)
TacoBot().init(ctx, bus)
class Channel: def init(self, messages: List[str]): self.messages = messages self.awayMap: Dict[str, str] = {} self.tacoMap: Dict[str, int] = {}
def push(self, line: str):
self.messages.append(line)
class EventBus: def init(self): self.handlers: Dict[Type, List[Callable]] = defaultdict(list)
def subscribe(self, eventType: Type, handler: Callable):
self.handlers[eventType].append(handler)
def publish(self, event: Any):
eventType = type(event)
if eventType in self.handlers:
for handler in self.handlers[eventType]:
handler(event)
class UserMessageEvent: def init(self, name: str, text: str): self.name = name self.text = text
class AwaySetEvent: def init(self, name: str, reason: str): self.name = name self.reason = reason
class AwayNotifyEvent: def init(self, name: str, reason: str): self.name = name self.reason = reason
class Bot: def init(self, context, eventBus): pass
class AwayBot(Bot): def init(self, context, eventBus): self.context = context self.eventBus = eventBus
eventBus.subscribe(UserMessageEvent, self.handleUserMessage)
eventBus.subscribe(AwayNotifyEvent, self.handleAwayNotify)
eventBus.subscribe(AwaySetEvent, self.handleAwaySet)
def handleUserMessage(self, event):
text = event.text
if text.startswith("/away "):
reason = text[6:]
name = event.name
self.context.push(f"AwayBot: {name} is away: {reason}")
self.eventBus.publish(AwaySetEvent(name, reason))
def handleAwayNotify(self, event):
self.context.push(f"AwayBot: {event.name} is away: {event.reason}")
def handleAwaySet(self, event):
self.context.awayMap[event.name] = event.reason
class MeetBot(Bot): def init(self, context, eventBus): self.context = context self.eventBus = eventBus
eventBus.subscribe(UserMessageEvent, self.handleUserMessage)
def handleUserMessage(self, event):
text = event.text
if text.startswith("/meet "):
target = text[6:]
sender = event.name
awayMap = self.context.awayMap
# Check if either participant is away and notify
if target in awayMap:
self.eventBus.publish(AwayNotifyEvent(target, awayMap[target]))
# Announce the meeting
self.context.push(
f"MeetBot: Google Meet with @{sender}, and {target} starting at /abc-def-123")
# Mark both as away
self.eventBus.publish(AwaySetEvent(sender, f"@{sender} may be in a meeting right now"))
self.eventBus.publish(AwaySetEvent(target, f"@{target} may be in a meeting right now"))
class TacoBot(Bot): def init(self, context, eventBus): self.context = context
eventBus.subscribe(UserMessageEvent, self.handleUserMessage)
def handleUserMessage(self, event):
text = event.text
if text.startswith("/givetaco "):
parts = text.split(" ")
if len(parts) >= 2:
recipient = parts[1]
giver = event.name
tacoCount = 1
if len(parts) >= 3:
tacoCount = int(parts[2])
tacoMap = self.context.tacoMap
currentTacos = 0
if recipient in tacoMap:
currentTacos = tacoMap[recipient]
newTacos = currentTacos + tacoCount
tacoMap[recipient] = newTacos
tacoWord = "taco" if tacoCount == 1 else "tacos"
totalTacoWord = "taco" if newTacos == 1 else "tacos"
self.context.push(f"TacoBot: @{giver} gave {recipient} {tacoCount} {tacoWord}. {recipient} now has {newTacos} {totalTacoWord}.")
def test1(): print("===== Test 1 =====") app = ChatApp()
app.sendMessage("Alice", "Hello")
app.sendMessage("Bob", "Hi")
app.sendMessage("Alice", "Nice job on your presentations")
app.sendMessage("Cindy", "/givetaco @justin")
app.sendMessage("Bob", "/givetaco @justin 2")
app.sendMessage("Alice", "Bob let's meet")
app.sendMessage("Bob", "/meet Alice")
app.sendMessage("David", "/away out for lunch")
app.sendMessage("Emily", "Anyone around?")
app.sendMessage("Frank", "/meet David")
print(app.getMessages())
# Expected: ["Alice: Hello",
# "Bob: Hi",
# "Alice: Nice job on your presentations",
# "Cindy: /givetaco @justin",
# "TacoBot: @Cindy gave @justin 1 taco. @justin now has 1 taco.",
# "Bob: /givetaco @justin 2",
# "TacoBot: @Bob gave @justin 2 tacos. @justin now has 3 tacos.",
# "Alice: Bob let's meet",
# "Bob: /meet Alice",
# "MeetBot: Google Meet with @Bob, and Alice starting at /abc-def-123",
# "David: /away out for lunch",
# "AwayBot: David is away: out for lunch",
# "Emily: Anyone around?",
# "Frank: /meet David",
# "AwayBot: David is away: out for lunch",
# "MeetBot: Google Meet with @Frank, and David starting at /abc-def-123"]
def test2(): print("\n===== Test 2 =====") app = ChatApp()
app.sendMessage("Alice", "/away on vacation")
app.sendMessage("Bob", "/away sick today")
app.sendMessage("Charlie", "/meet Alice")
app.sendMessage("David", "Hello everyone")
app.sendMessage("Eve", "/meet Bob")
print(app.getMessages())
# Expected: ["Alice: /away on vacation",
# "AwayBot: Alice is away: on vacation",
# "Bob: /away sick today",
# "AwayBot: Bob is away: sick today",
# "Charlie: /meet Alice",
# "AwayBot: Alice is away: on vacation",
# "MeetBot: Google Meet with @Charlie, and Alice starting at /abc-def-123",
# "David: Hello everyone",
# "Eve: /meet Bob",
# "AwayBot: Bob is away: sick today",
# "MeetBot: Google Meet with @Eve, and Bob starting at /abc-def-123"]
if name == "main": test1() test2() `
Channel class manages the state, like away statuses and taco counts.