93. Design Live News Feed System

Design Live News Feed System
Design and implement a Live News Feed System.

News providers publish articles under topics such as sports, tech, finance and many other topics. Users can subscribe to topics they care about, receive near real-time notifications when new articles are published for those topics, and fetch a personalized feed.

Functional Requirements

  • News providers publish content.
  • Each article belongs to one or more topics.
  • Users subscribe or unsubscribe from one or more topics.
  • When a new article is published, subscribed users receive notifications.
    Each user may receive at max one notification for an article even when it is subscribed to multiple topics which the article contains.
  • Users can fetch a personalized feed based on their current subscriptions.
  • The system should be extensible for future features such as ranking, filtering, muting topics, and pagination.

Important Notes

  • All data should be stored in memory.
  • All IDs and names are case-sensitive.
  • Unsubscribing removes only future notifications; past notifications already delivered remain available until fetched.
  • Feed ordering should be deterministic.
  • When multiple articles have the same publish timestamp, order them by lexicographically smaller articleId first.
  • Personalized feed should contain only articles published after the user subscribed and whose atleast one topic is currently subscribed by the user at the time of fetching the feed.
  • Notifications should contain only articles published after the user subscribed to that topic.
    If a user unsubcribes from a topic then past queued notifications for that topic are not affected and they will be delivered.

Constructor

LiveNewsFeed(List<String> existingUserIds, List<String> existingTopicIds)

  • Each item in existingTopicids or existingUserIds is non-blank and globally unique.
  • existingTopicIds.size()>0
  • existingUserIds.size()>0

Method Signatures

boolean subscribeUnsubscribeTopic(String userId, String topicId, boolean subscribe, int currentTime)

  • if subcribe = true then subscribes the given user to the given topic else unsubscribes.
  • If system was already in desired state then nothing is updated and false is returned.
  • Returns true if the subscribe/unsubscribe operation is performed successfully
  • Returns false if the user or topic does not exist.
  • currentTime is number of minutes elapsed since 1 Jan 1970, it is monotonically increasing (non-decreasing).

boolean publishArticle(String articleId, String title, List<String> topics, int currentTime)

  • Publishes a new article under the given topic.
  • Returns true if the article is published successfully.
  • Returns false if articleId already exists, any element in topics does not exist, currentTime is negative or input is invalid.
  • Publishing an article should enqueue notifications for all users currently subscribed to that topic.
  • currentTime is number of minutes elapsed since 1 Jan 1970, it is monotonically increasing (non-decreasing).

List<String> getPersonalizedFeed(String userId)

  • Returns the personalized feed for the given user.
  • An article appears in the personalized feed if there exists at least one topic in that article such that the user was subscribed to that topic at publish time and is still subscribed to that topic at feed fetch time.
  • Each article must be returned as a string because object outputs should be represented in string form.
  • Use the format "articleId | title | currentTime".
  • Order by currentTime descending; if tied, order by lexicographically smaller articleId first.
  • If the user does not exist, return an empty list.

List<String> getNotifications(String userId)

  • Returns and clears all pending notifications for the given user.
  • Each notification must be returned as a string.
  • Use the format "articleId | title | currentTime".
  • Order by delivery sequence, which for this problem matches article publish order.
  • If the user does not exist, return an empty list.

Constraints

  • 1 ≤ userId.length(), topicId.length(), articleId.length() ≤ 10
  • 0 ≤ title.length() ≤ 100
  • All ids and title contains only characters from a-z, A-Z and 0-9, title also contains space character.
  • currentTime is monotonically increasing (non decreasing) across all method calls.
  • 0 ≤ currentTime ≤ 10^8
  • At most 10^4 users
  • At most 10^3 topics
  • At most 10^5 articles
  • Total number of method calls will not exceed 2 * 10^5
  • All operations should be efficient enough for an in-memory interview setting.

Examples

Example 1

  • LiveNewsFeed(existingUserIds = ["u1", "u2"], existingTopicIds = ["sports", "tech"]) Output: null
    Creates a system with two existing users and two existing topics.
  • subscribeUnsubscribeTopic(userId = "u1", topicId = "sports", subscribe = true, currentTime = 10) Output: true
    User u1 subscribes to sports at time 10.
  • subscribeUnsubscribeTopic(userId = "u1", topicId = "tech", subscribe = true, currentTime = 20) Output: true
    User u1 subscribes to tech at time 20.
  • subscribeUnsubscribeTopic(userId = "u2", topicId = "tech", subscribe = true, currentTime = 30) Output: true
    User u2 subscribes to tech at time 30.
  • publishArticle(articleId = "a1", title = "Final Score", topics = ["sports"], currentTime = 100) Output: true
    Article a1 is published under sports, so only users currently subscribed to sports at time 100 are notified.
  • publishArticle(articleId = "a2", title = "New Phone Launch", topics = ["tech"], currentTime = 110) Output: true
    Article a2 is published under tech, so users currently subscribed to tech at time 110 are notified.
  • publishArticle(articleId = "a3", title = "Smart Watch Finals", topics = ["sports", "tech"], currentTime = 120) Output: true
    Article a3 belongs to both topics. User u1 is subscribed to both at publish time, but still receives only one notification for this article.
  • getNotifications(userId = "u1") Output: ["a1 | Final Score | 100", "a2 | New Phone Launch | 110", "a3 | Smart Watch Finals | 120"]
    User u1 was subscribed to the relevant topics when all three articles were published. Even though a3 matches two subscribed topics, it appears only once.
  • getNotifications(userId = "u2") Output: ["a2 | New Phone Launch | 110", "a3 | Smart Watch Finals | 120"]
    User u2 subscribed only to tech, so notifications are returned only for articles that matched tech at publish time.
  • getPersonalizedFeed(userId = "u1") Output: ["a3 | Smart Watch Finals | 120", "a2 | New Phone Launch | 110", "a1 | Final Score | 100"]
    User u1 is still subscribed to both topics, so all qualifying articles appear in descending publish time order.
  • getPersonalizedFeed(userId = "u2") Output: ["a3 | Smart Watch Finals | 120", "a2 | New Phone Launch | 110"]
    User u2 is currently subscribed only to tech, so only articles containing tech appear in the feed.

Example 2

  • LiveNewsFeed(existingUserIds = ["u1"], existingTopicIds = ["finance"]) Output: null
    Creates a system with one existing user and one existing topic.
  • subscribeUnsubscribeTopic(userId = "u1", topicId = "finance", subscribe = true, currentTime = 50) Output: true
    User u1 subscribes to finance at time 50.
  • publishArticle(articleId = "a1", title = "Market Opens Higher", topics = ["finance"], currentTime = 200) Output: true
    Since u1 is subscribed at publish time, a notification for a1 is queued.
  • subscribeUnsubscribeTopic(userId = "u1", topicId = "finance", subscribe = false, currentTime = 220) Output: true
    User u1 unsubscribes from finance at time 220. This removes only future eligibility.
  • publishArticle(articleId = "a2", title = "Closing Bell", topics = ["finance"], currentTime = 250) Output: true
    Article a2 is published after the unsubscribe, so no new notification is queued for u1.
  • getNotifications(userId = "u1") Output: ["a1 | Market Opens Higher | 200"]
    Past pending notifications remain available until fetched, so a1 is still returned.
  • getPersonalizedFeed(userId = "u1") Output: []
    At feed fetch time, u1 is no longer subscribed to finance, so neither a1 nor a2 appears in the feed.

Example 3

  • LiveNewsFeed(existingUserIds = ["u1"], existingTopicIds = ["tech"]) Output: null
    Creates a system with one existing user and one existing topic.
  • subscribeUnsubscribeTopic(userId = "u1", topicId = "tech", subscribe = true, currentTime = 250) Output: true
    User u1 subscribes to tech before both articles are published.
  • publishArticle(articleId = "a2", title = "AI Update", topics = ["tech"], currentTime = 300) Output: true
    Article a2 is published first, so its notification is delivered first.
  • publishArticle(articleId = "a1", title = "Chip Update", topics = ["tech"], currentTime = 300) Output: true
    Article a1 has the same publish timestamp as a2, which demonstrates the feed tie-break rule.
  • getPersonalizedFeed(userId = "u1") Output: ["a1 | Chip Update | 300", "a2 | AI Update | 300"]
    Feed ordering uses publish time descending and then lexicographically smaller articleId first, so a1 appears before a2.
  • getNotifications(userId = "u1") Output: ["a2 | AI Update | 300", "a1 | Chip Update | 300"]
    Notifications follow delivery sequence, which here matches publish order, so a2 appears before a1.




Please use Laptop/Desktop or any other large screen to add/edit code.