Early Access: 87 spots left.

Claim
Low Level DesignInterview QuestionsDesign an In-Memory Pub/Sub System

Course content

Design an In-Memory Pub/Sub System

Medium·Tagsllddesignpub-subobserverevent-driven

Problem Statement

Design an in-memory publish/subscribe broker. Subscribers register interest in named topics; publishers post messages to topics; the broker delivers each message to every current subscriber of that topic. API contract ```java interface Subscriber { void onMessage(String topic, String message); } class PubSubBroker { public PubSubBroker(); /** Register `handler` for `topic`. Returns a unique subscription id. The same handler * can subscribe to the same topic more than once — each subscription is independent. */ public String subscribe(String topic, Subscriber handler); /** Deliver `message` to every current subscriber of `topic` (in subscription order, single- * threaded). Returns the number of subscribers notified. Publishing to a topic with no subscribers returns 0. / public int publish(String topic, String message); /** Remove a subscription by id. Returns true if it was active (and is now removed), * false if the id is unknown or already removed. */ public boolean unsubscribe(String subscriptionId); /* Current number of active subscribers on `topic`. Returns 0 for unknown topics. / public int subscriberCount(String topic); } ``` The validator runs three checks: 1. subscribe_publish_and_topic_isolation — subscribe two handlers to topic `"news"` and one to `"sports"`; `publish("news", "hello")` returns 2 and only the news subscribers receive the message; `publish("sports", "goal")` returns 1 and only the sports subscriber sees it. Publishing to an unknown topic returns 0. 2. multiple_subscribers_all_receive — five subscribers on `"events"`; `publish("events", "x")` notifies all five; each handler's onMessage was called exactly once with the same arguments. 3. unsubscribe_stops_delivery — subscribe, publish (received), `unsubscribe(id)`, publish (NOT received). Unsubscribing an unknown id returns `false`. Subscribing the same handler twice and unsubscribing one id leaves the other live. The stub class throws `UnsupportedOperationException` from every method.

Examples

Example 1
Input
var b = new PubSubBroker(); b.subscribe("news", h1); b.subscribe("news", h2); b.publish("news", "hi")
Output
"2"
Why
Both subscribers receive the message. publish returns the count of notified handlers.
Example 2
Input
var id = b.subscribe("news", h); b.publish("news", "a"); b.unsubscribe(id); b.publish("news", "b")
Output
"h received only \"a\""
Why
After unsubscribe, the handler stops receiving messages. The second publish returns 0.
Example 3
Input
b.publish("unknown-topic", "hello")
Output
"0"
Why
Publishing to a topic with no subscribers is a no-op; the broker does not throw.

Constraints

  • Subscribers are notified in subscription order (FIFO).
  • publish to a topic with no subscribers returns 0; never throws.
  • subscribe accepts the same handler for the same topic multiple times; each call returns a different subscription id.
  • unsubscribe by id is O(1) on average; returns true exactly when it removed an active subscription.
  • All operations are single-threaded for this exercise — no concurrency concerns.

Hints

Stuck? Reveal a nudge toward the right pattern, one step at a time.

Hint 1
Two maps make this clean: `Map<String, LinkedHashMap<String, Subscriber>> topicToSubs` (topic → ordered map of subId → handler) for delivery in subscription order, and `Map<String, String> subToTopic` (subId → topic) for O(1) unsubscribe.
Hint 2
subscribe: mint a new id ('S-' + counter++). Look up or create the inner LinkedHashMap for the topic. Put subId → handler. Also put subId → topic into subToTopic. Return the id.
Hint 3
publish: get the inner map for the topic. If null or empty, return 0. Otherwise iterate values() in insertion order and call onMessage on each. Return the count.
Hint 4
unsubscribe: look up the topic via subToTopic. If absent, return false. Remove subId from both maps. Return true.
Hint 5
subscriberCount: get inner map; return 0 if null else inner.size().
Hint 6
LinkedHashMap is the right inner container — it preserves insertion order (so delivery is FIFO), and remove(key) is O(1).