Build a ChatOps Command Dispatcher That Matches Named Arguments
GoogleSoftware Engineer · Technical ScreenLast updated
Implement a command dispatcher for a chat ops bot that resolves slash-separated commands with named arguments to a registered handler. Tests trie-based matching, precedence between literal and parameter segments, and handling of ambiguous or dead-end routes.
You're building the command dispatcher for a chat ops bot. Teammates type commands like /deploy prod status in a chat channel, and the bot must find the right handler for each one. Internally, a command is represented as a slash-separated string, and you need to support two operations:
register(pattern, handler)— registers a handler for a command pattern.resolve(command)— finds the handler whose pattern matches a concrete command string, and returns it together with the values captured by any named arguments.
A pattern segment written as :name is a named argument and matches exactly one token: the pattern deploy/:service/status matches the command deploy/prod/status with service = "prod". A segment without a leading colon must match literally.
Assume a helper already exists to split a command into tokens:
def tokenize(command: str) -> list[str]:
"""'deploy/prod/status' -> ['deploy', 'prod', 'status']"""
A starting interface (feel free to adjust it once you've made your design decisions):
from typing import Callable
Handler = Callable[..., object]
class CommandRouter:
def register(self, pattern: str, handler: Handler) -> None: ...
def resolve(self, command: str) -> tuple[Handler, dict[str, str]] | None: ...
Example
register("health", health_handler)
register("deploy/prod/status", status_prod_handler)
register("deploy/:service/status", status_any_handler)
register("deploy/prod/logs", logs_prod_handler)
register("deploy/:service/restart", restart_any_handler)
resolve("health") -> (health_handler, {})
resolve("deploy/prod/status") -> (status_prod_handler, {})
resolve("deploy/staging/status") -> (status_any_handler, {"service": "staging"})
resolve("deploy/prod/restart") -> ???
resolve("deploy/staging/restart") -> (restart_any_handler, {"service": "staging"})
Constraints
- Up to 10,000 registered patterns, each with up to 20 segments, and up to 10^5
resolvecalls. Aresolveshould not need to test the command against every registered pattern. tokenizehas already removed any surrounding slashes; you don't need to write it.
Decide and State
- When both a literal segment and a named argument could match at the same position — like
deploy/prod/statusversusdeploy/:service/statusmatching the commanddeploy/prod/status— which one wins? - If a literal branch matches the first segments but has no route for the rest, as in
resolve("deploy/prod/restart")above (wheredeploy/prod/logsexists but notdeploy/prod/restart), should the router fall back and trydeploy/:service/restartinstead? Pick a behavior, implement it, and be ready to defend the trade-off.
Hints
Hint 1
Many patterns share a prefix. Build a structure that lets a lookup walk the command once, segment by segment, instead of testing it against every registered pattern.
Hint 2
At each node, store at most one literal child per segment value and at most one named-argument child, plus its parameter name.
Hint 3
Pick one precedence rule for literal vs. named argument at the same node and apply it consistently everywhere, including at the final segment.
Hint 4
Decide whether a dead end on a literal branch should backtrack into a sibling named-argument branch, and know what that trade-off costs you.
Follow-up questions
- How would you add a trailing wildcard segment, like `files/*rest`, that captures everything remaining?
- How would you add backtracking so `deploy/prod/restart` could still fall through to `deploy/:service/restart` when the literal branch dead-ends, without turning every lookup into a full search?
- How would you extend this to dispatch on both a command and a method, like a GET and a POST on the same pattern?
- How would you support registering and unregistering patterns at runtime while other threads are calling resolve?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.