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).