Implement an Extensible Chatbot App
Company: OpenAI
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Technical Screen
Implement a `ChatApp` class that processes user messages and routes them to bot responders.
The system must support multiple **bot types**, and the design must be **extensible**: adding a brand-new bot type should require *zero* changes to existing `ChatApp` code. You are designing the in-memory application objects and their interfaces — not a distributed system — so focus on clean class design, the right abstractions, and clear extension points.
### Constraints & Assumptions
- Single-process, in-memory implementation (no database, network, or RPC layer required).
- A message has at minimum a sender identity and text content.
- Multiple bots may be registered at once; each bot independently decides whether and how to respond to a given message.
- A bot may need to remember context across messages (e.g. a multi-turn flow), so each bot needs somewhere to keep per-conversation state.
- Bots are added over time by other developers — you control the `ChatApp` core, but not the bot implementations.
- You may use any mainstream language; the reference answer uses Python.
### Clarifying Questions to Ask
- Should every registered bot receive every message, or should bots subscribe to specific message types / channels?
- Do bot responses themselves get re-dispatched to other bots (can a bot reply to a bot), or do only user messages trigger dispatch?
- Is ordering of bot responses significant — must bots run in registration order, or by some priority?
- Are bots registered globally for the whole app, or can each channel have a different set of bots?
- What should happen if one bot raises an exception — fail the whole send, or skip that bot?
- Do we need concurrency safety (multiple users sending into the same channel at once), or is single-threaded acceptable?
### Part 1 — Extensible single-channel `ChatApp`
Design and implement `ChatApp` so that:
- A user can **send a message** to the app.
- The app **dispatches** that message to every registered bot.
- Each bot **decides whether and how to respond** (it may produce a response, or stay silent).
- The app **records a message log** containing both user messages and bot responses.
- New bot implementations are added purely through **registration / dependency injection** — never by editing `ChatApp`.
Show the bot interface, the `ChatApp` core, and at least one concrete example bot demonstrating that adding a bot needs no change to `ChatApp`.
```hint Design principle
"A new bot type must not modify existing code" points squarely at one of the SOLID principles. Which one? Once you name it, ask what language feature normally lets you satisfy it instead of a type switch.
```
```hint Where state lives
You want one bot *instance* to be reusable across many conversations, yet each conversation needs its own memory. So where can per-conversation state live such that it is *not* a field on the bot object? Think about who hands the bot its memory on each call.
```
```hint Decoupling the dispatch
Picture the core send path. What is the *most* `ChatApp` should be allowed to know about the bots it loops over? If the answer is "nothing but the interface," what does that imply the loop can and cannot mention?
```
#### What a Strong Answer Covers
- A clean **bot abstraction** (one contract every bot implements) so the app talks to bots through a single interface.
- A `ChatApp` dispatch loop with **no knowledge of concrete bot types** — no `isinstance`/type switch (Open/Closed Principle).
- A **registration / DI mechanism** for adding bots, demonstrated with at least one concrete bot added with zero `ChatApp` edits.
- Correct **per-conversation state ownership** — the app hands each bot its state slice, so a single bot instance stays reusable and stateless.
- A **message log** that captures both inbound user messages and outbound bot responses.
### Part 2 — Multi-channel support (follow-up)
Extend the design so the app supports **multiple channels** (think Slack channels or DMs). Each channel must have **fully independent state**, including:
- Its own message log.
- Its own per-bot state, so the *same* bot can hold different context in different channels.
Decide and justify whether bots are registered globally (shared across channels) or per-channel, and make sure adding a channel does not require touching the dispatch logic.
```hint Where to start
Look at what `ChatApp` currently owns. Which of those things are inherently per-channel, and which (if any) could stay shared across channels? If you grouped the per-channel pieces together and looked them up by channel, would the dispatch loop have to change at all?
```
```hint Edge cases worth surfacing
Walk through the lifecycle of channels and bot registrations and ask which orderings could leave a channel or a bot without the state it expects. Two orderings in particular need explicit handling — name them to the interviewer before you code.
```
#### What a Strong Answer Covers
- **Channel-isolated state** — each channel keeps its own message log and its own per-bot state, so the same bot holds different context per channel.
- A clean extraction of the per-channel pieces (e.g. a `ChannelState` value object keyed by `channel_id`) so the **dispatch loop is unchanged**.
- A clear, **justified stance** on global-vs-per-channel bot registration (and what would change if the other choice were required).
- Explicit handling of the **lifecycle orderings** — a message arriving for an unknown channel, and a bot registered after channels already exist.
### What a Strong Answer Covers
These dimensions span both parts:
- A consistent design where Part 2 is a *cheap extension* of Part 1 — the only thing that changes is *where* the log and bot state live, not the dispatch contract.
- Discussion of the obvious extension knobs the interviewer will probe: bot ordering/priority, error isolation, subscription/filtering, and thread safety.
- Clear reasoning about why state is **passed into** the bot rather than stored on it, and how that single decision pays off for multi-channel.
### Follow-up Questions
- How would you let bots **subscribe** to only certain messages (e.g. by command prefix or message type) instead of receiving everything, without bloating `ChatApp`?
- How would you add **error isolation** so a single misbehaving bot cannot break the message flow for the channel?
- If bot responses were **asynchronous** (a bot calls an external API), how would the dispatch contract and the message log change?
- How would you make this **thread-safe** if many users send into the same channel concurrently, and where exactly are the race conditions?
Quick Answer: This question evaluates a candidate's ability to design extensible object-oriented systems, covering interface-based architecture, dependency injection, per-conversation state management, and decoupled dispatch logic.