Katkat

Implement Article Vote Tracking

RipplingSoftware Engineer · Technical Screen

Last updated

Design an in-memory article vote tracking system that manages user upvotes/downvotes, detects vote flips to maintain an LRU-style flip history per user, and provides top-k article rankings by net score.

Problem Statement

Design an article vote tracking system that manages user upvotes and downvotes, tracks voting history, detects vote "flips", and ranks articles based on popularity.

Implement the ArticleVoteTracker class:

  • ArticleVoteTracker(): Initializes the tracker.
  • vote(user_id: int, article_id: int, vote_type: int) -> None:
    • Records a vote for article_id by user_id. vote_type is either 1 (upvote) or -1 (downvote).
    • If the user has not voted on this article before, record the vote and adjust the article's net score.
    • If the user votes the same as their existing vote on this article, do nothing.
    • If the user votes opposite to their previous vote (i.e., changing from 1 to -1 or -1 to 1), this is considered a flip. Update the article's net score accordingly and record this article in the user's flip history as the most recently flipped article. If the article was already in the user's flip history, move it to the most recent position (maintaining unique article IDs).
  • get_score(article_id: int) -> int:
    • Returns the current net score (total upvotes minus total downvotes) of article_id. If the article has received no votes, return 0.
  • get_user_flips(user_id: int, limit: int) -> list[int]:
    • Returns a list of up to limit unique article IDs that user_id has flipped, ordered from most recently flipped to least recently flipped.
  • get_top_articles(k: int) -> list[int]:
    • Returns the top k article IDs with the highest net scores among all articles that have ever received at least one vote. If scores are tied, prioritize the smaller article_id. If fewer than k articles exist, return all of them in ranked order.

Example

tracker = ArticleVoteTracker()

# User 1 upvotes article 101, User 2 downvotes article 101
tracker.vote(user_id=1, article_id=101, vote_type=1)  # score: 1
tracker.vote(user_id=2, article_id=101, vote_type=-1) # score: 0
assert tracker.get_score(101) == 0

# User 1 upvotes article 102
tracker.vote(user_id=1, article_id=102, vote_type=1)  # score: 1

# User 1 flips their vote on article 101 from +1 to -1
tracker.vote(user_id=1, article_id=101, vote_type=-1) # score: -2 (User 1 is -1, User 2 is -1)
assert tracker.get_score(101) == -2
assert tracker.get_user_flips(1, 5) == [101]

# User 1 flips article 102 from +1 to -1
tracker.vote(user_id=1, article_id=102, vote_type=-1)
assert tracker.get_user_flips(1, 5) == [102, 101]

# Top articles: article 102 has score -1, article 101 has score -2
assert tracker.get_top_articles(2) == [102, 101]

Constraints

  • 1 <= user_id, article_id <= 10^9
  • vote_type in (1, -1)
  • 1 <= limit, k <= 1000
  • At most 2 * 10^5 total calls across all methods.
  • get_top_articles should run efficiently even if the total number of articles is large.

Hints

Hint 1

To track each user's flip history with uniqueness and recency, an OrderedDict or a doubly linked list combined with a hash map per user works in O(1) time.

Hint 2

A flip from +1 to -1 changes the net score by -2, and from -1 to +1 changes it by +2.

Hint 3

For dynamic ranking, you can use a min-heap of size k over all tracked articles on query, or maintain article scores in a balanced BST / bucket structure to make top-k queries faster.

Follow-up questions

  • How would you optimize `get_top_articles` if calls to it are very frequent compared to `vote` calls?
  • How would you support vote cancellations (i.e., a user removes their vote entirely), and how would that affect flip history?
  • How would you scale this design across multiple distributed worker nodes while maintaining consistency of scores and user flip histories?