168. Design WhatsApp Read Receipts

Asked in

Design WhatsApp Read Receipts
Design a WhatsAppReadReceipts feature.

Assume WhatsApp is already built.

The observer system that receives message sent, delivered, and read events is also already built.

Your task is only to design the read receipt feature that tracks and returns the correct tick status for each message.

A message can show one gray tick, two gray ticks, or two blue ticks depending on whether the message was sent, delivered, or read.

The feature must support direct conversations and group conversations.

A direct conversation has exactly two users.

A group conversation has two or more users.

For every message, the sender is the only user who can see the tick status of that message.

Class Signature

WhatsAppReadReceipts(List<String> conversationConfigs, List<String> userSettings)

Constructor Parameters

conversationConfigs contains conversation configuration strings.

Each string has the format:

"conversationId,conversationType,userId1|userId2|userId3"

conversationType is either DIRECT or GROUP.

Example:

"chat1,DIRECT,alice|bob"

"team1,GROUP,alice|bob|charlie"

userSettings contains read receipt setting strings.

Each string has the format:

"userId,readReceiptSetting"

readReceiptSetting is either ON or OFF.

If a user is not present in userSettings, their read receipt setting is considered ON.

Method Signatures

String sendMessage(String messageId, String conversationId, String senderUserId, int currentTime)
  • Creates a new message inside the given conversation.
  • The sender must be a participant of the conversation.
  • A newly sent message starts with one gray tick.
  • If the request is invalid, return "INVALID_REQUEST".
  • If the request is valid, return "MESSAGE_SENT".
String markDelivered(String messageId, String userId, int currentTime)
  • Records that the message was delivered to the given user.
  • The user must be a recipient of the message.
  • The sender cannot be marked as delivered for their own message.
  • If the request is invalid, return "INVALID_REQUEST".
  • If the message was already delivered or read by this user, return "NO_CHANGE".
  • If delivery is newly recorded, return "DELIVERY_RECORDED".
String markRead(String messageId, String userId, int currentTime)
  • Records that the message was read by the given user.
  • The user must be a recipient of the message.
  • The sender cannot be marked as read for their own message.
  • If a message is marked as read, it is also considered delivered to that user.
  • If the request is invalid, return "INVALID_REQUEST".
  • If the message was already read by this user, return "NO_CHANGE".
  • If read is newly recorded, return "READ_RECORDED".
String getReceiptStatus(String messageId, String viewerUserId, int currentTime)
  • Returns the visible tick status of the message for the given viewer.
  • Only the sender of the message can view its receipt status.
  • If the message does not exist, return "INVALID_REQUEST".
  • If the viewer is not the sender, return "NOT_VISIBLE".
List<String> getGroupReadSummary(String messageId, String viewerUserId, int currentTime)
  • Returns detailed delivery and read information for every recipient of a group message.
  • Return an empty list for any invalid request, including empty input values, missing message, non-sender viewer, direct conversation, and decreasing currentTime.
  • Only the sender of the group message can view this summary.

Receipt Status Rules

For a direct conversation:
  • Return "SENT_ONE_GRAY_TICK" if the message has been sent but not delivered to the recipient.
  • Return "DELIVERED_TWO_GRAY_TICKS" if the message has been delivered to the recipient but is not visibly read.
  • Return "READ_TWO_BLUE_TICKS" if the message has been read by the recipient and both sender and recipient have read receipts set to ON.
  • If either the sender or recipient has read receipts set to OFF, a read direct message must still show "DELIVERED_TWO_GRAY_TICKS".
For a group conversation:
  • Return "SENT_ONE_GRAY_TICK" if at least one recipient has not received the message.
  • Return "DELIVERED_TWO_GRAY_TICKS" if all recipients have received the message but at least one recipient has not read it.
  • Return "READ_TWO_BLUE_TICKS" if all recipients have read the message.
  • Group read receipt status is not affected by individual read receipt settings.

Group Read Summary Format

Each string returned by getGroupReadSummary must have the format:

"userId,deliveryStatus,deliveredTime,readStatus,readTime"

deliveryStatus is either DELIVERED or NOT_DELIVERED.

readStatus is either READ or NOT_READ.

If the message has not been delivered to a user, deliveredTime must be -1.

If the message has not been read by a user, readTime must be -1.

The returned list must be sorted by userId in ascending lexicographic order.

Invalid Request Rules

Return "INVALID_REQUEST" for string-returning methods when:
  • messageId is empty.
  • conversationId is empty.
  • senderUserId is empty.
  • userId is empty.
  • viewerUserId is empty.
  • The conversation does not exist.
  • The sender is not part of the conversation.
  • The message already exists when calling sendMessage.
  • The message does not exist when calling delivery, read, or receipt methods.
  • The user being marked delivered or read is not a recipient of the message.
  • The sender is marked delivered or read for their own message.
  • currentTime is smaller than the previous timestamp used in any method call.

Constraints

  • 1 ≤ conversationConfigs.size() ≤ 10^4
  • 0 ≤ userSettings.size() ≤ 10^4
  • 1 ≤ number of method calls ≤ 10^5
  • 1 ≤ messageId.length() ≤ 50
  • 1 ≤ conversationId.length() ≤ 50
  • 1 ≤ userId.length() ≤ 50
  • 0 ≤ currentTime ≤ 10^9
  • currentTime values across all method calls must be monotonically non-decreasing.
  • All ids contain only lowercase English letters, digits, hyphen, or underscore.
  • Every valid direct conversation has exactly two distinct users.
  • Every valid group conversation has at least two distinct users.
  • The input will never use null as a parameter value.

Examples

Example 1: Direct Message With Read Receipts On

WhatsAppReadReceipts(conversationConfigs = List.of("chat1,DIRECT,alice|bob"), userSettings = List.of("alice,ON", "bob,ON"))

sendMessage(messageId = "m1", conversationId = "chat1", senderUserId = "alice", currentTime = 1) returns "MESSAGE_SENT"

getReceiptStatus(messageId = "m1", viewerUserId = "alice", currentTime = 2) returns "SENT_ONE_GRAY_TICK"

markDelivered(messageId = "m1", userId = "bob", currentTime = 3) returns "DELIVERY_RECORDED"

getReceiptStatus(messageId = "m1", viewerUserId = "alice", currentTime = 4) returns "DELIVERED_TWO_GRAY_TICKS"

markRead(messageId = "m1", userId = "bob", currentTime = 5) returns "READ_RECORDED"

getReceiptStatus(messageId = "m1", viewerUserId = "alice", currentTime = 6) returns "READ_TWO_BLUE_TICKS"

Example 2: Direct Message With Read Receipts Off

WhatsAppReadReceipts(conversationConfigs = List.of("chat1,DIRECT,alice|bob"), userSettings = List.of("alice,ON", "bob,OFF"))

sendMessage(messageId = "m1", conversationId = "chat1", senderUserId = "alice", currentTime = 1) returns "MESSAGE_SENT"

markRead(messageId = "m1", userId = "bob", currentTime = 2) returns "READ_RECORDED"

getReceiptStatus(messageId = "m1", viewerUserId = "alice", currentTime = 3) returns "DELIVERED_TWO_GRAY_TICKS"

Bob has read the message, but Bob's read receipt setting is OFF, so Alice cannot see blue ticks.

Example 3: Group Message

WhatsAppReadReceipts(conversationConfigs = List.of("team1,GROUP,alice|bob|charlie"), userSettings = List.of("alice,ON", "bob,OFF", "charlie,ON"))

sendMessage(messageId = "g1", conversationId = "team1", senderUserId = "alice", currentTime = 10) returns "MESSAGE_SENT"

markDelivered(messageId = "g1", userId = "bob", currentTime = 11) returns "DELIVERY_RECORDED"

getReceiptStatus(messageId = "g1", viewerUserId = "alice", currentTime = 12) returns "SENT_ONE_GRAY_TICK"

markDelivered(messageId = "g1", userId = "charlie", currentTime = 13) returns "DELIVERY_RECORDED"

getReceiptStatus(messageId = "g1", viewerUserId = "alice", currentTime = 14) returns "DELIVERED_TWO_GRAY_TICKS"

markRead(messageId = "g1", userId = "bob", currentTime = 15) returns "READ_RECORDED"

getReceiptStatus(messageId = "g1", viewerUserId = "alice", currentTime = 16) returns "DELIVERED_TWO_GRAY_TICKS"

markRead(messageId = "g1", userId = "charlie", currentTime = 17) returns "READ_RECORDED"

getReceiptStatus(messageId = "g1", viewerUserId = "alice", currentTime = 18) returns "READ_TWO_BLUE_TICKS"

Example 4: Group Read Summary

WhatsAppReadReceipts(conversationConfigs = List.of("team1,GROUP,alice|bob|charlie"), userSettings = List.of("alice,ON", "bob,ON", "charlie,ON"))

sendMessage(messageId = "g1", conversationId = "team1", senderUserId = "alice", currentTime = 10) returns "MESSAGE_SENT"

markRead(messageId = "g1", userId = "bob", currentTime = 12) returns "READ_RECORDED"

getGroupReadSummary(messageId = "g1", viewerUserId = "alice", currentTime = 13) returns List.of("bob,DELIVERED,12,READ,12", "charlie,NOT_DELIVERED,-1,NOT_READ,-1")

Bob is marked as read, so Bob is also considered delivered at time 12. Charlie has not received or read the message yet.

Example 5: Receipt Not Visible To Non Sender

WhatsAppReadReceipts(conversationConfigs = List.of("chat1,DIRECT,alice|bob"), userSettings = List.of("alice,ON", "bob,ON"))

sendMessage(messageId = "m1", conversationId = "chat1", senderUserId = "alice", currentTime = 1) returns "MESSAGE_SENT"

getReceiptStatus(messageId = "m1", viewerUserId = "bob", currentTime = 2) returns "NOT_VISIBLE"

Bob is not the sender of message m1, so Bob cannot view the tick status for Alice's sent message.




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