Message Event Aggregation
Reported by candidates from OpenAI's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.
Brute force dies here. OpenAI's Message Event Aggregation, reported in July 2026, hands you up to 10^5 events and 10^5 query timestamps, so rescanning every event per query is 10^10 work. It's a sliding window problem wrapped in per-user, per-chat state, with a memory rule that forces you to evict expired data. Events arrive out of order, ties keep input order, and output must be sorted by userId. If you blank mid-assessment, StealthCoder runs invisibly on your desktop as a safety net and reads the problem for you. Know the shape first and you probably won't need it.
The problem
This exercise combines the first three reported parts of a message-event aggregation task. Practice-version contract To make the reported task executable, this version uses the interface and deterministic conventions below. You are given a list of chat events and a list of query timestamps. Each event is encoded as a comma-separated string: timestamp,userId,chatId,eventType eventType is one of: message: a message sent in the chat. react: the user reacted in the chat, making the chat active for that user unless a later end_chat event ends it. end_chat: the user ended the chat. For each query time q, consider events in the inclusive five-minute window [q - 300, q]. Timestamps are integers measured in seconds. For every user with at least one event in the window, compute: messageCount: the number of message events for that user in the window. activeChatCount: the number of distinct chats whose latest react or end_chat event in the window is react. A message event does not change a chat's active state. Events may be given out of timestamp order. Process them by timestamp; when timestamps are equal, preserve their original input order. Return one string for each query, in the original query order. Format a user's result as userId:messageCount:activeChatCount, sort users lexicographically by userId, and join entries with semicolons. If the window contains no events, return an empty string for that query. Memory requirement As the five-minute window advances, discard expired aggregation state. Apart from storage used to order the input and hold the output, retained counting and chat-state data must be proportional to the events and user-chat pairs in the current window, not to every chat seen earlier. Function aggregateMessageEvents(events: String[], queryTimes: int[]) → String[] Examples Example 1 events = ["10,u1,c1,message", "20,u1,c1,react", "80,u2,c5,message", "90,u2,c5,react", "200,u1,c1,end_chat", "260,u1,c2,react", "400,u1,c2,message"] queryTimes = [260, 500] return = ["u1:1:1;u2:1:1", "u1:1:1"] At time 260, each user has one message in the window. Chat c2 is active for u1, while c5 is active for u2. At time 500, the window starts at 200; only u1 has events in the window, with one message and one active chat. Example 2 events = ["300,u2,c8,end_chat", "40,u1,c1,react", "100,u1,c1,end_chat", "70,u1,c2,react", "20,u2,c8,react", "60,u1,c2,message", "110,u2,c8,message"] queryTimes = [120, 320] return = ["u1:1:1;u2:1:1", "u1:1:1;u2:1:0"] The events are not given in timestamp order. At time 120, chat c8 is active for u2. At time 320, the later end_chat event makes it inactive. Chat c2 remains active for u1 at both query times. Constraints 0 <= events.length <= 10^5 0 <= queryTimes.length <= 10^5 0 <= timestamp, q <= 10^9 Every event has exactly four comma-separated fields and a valid eventType. userId and chatId contain no commas, colons, or semicolons.
Reported by candidates. Source: FastPrep
Pattern and pitfall
Stable-sort events by timestamp (ties keep input index). Sort queries by time too, but remember their original positions for output. Then use two pointers: advance a right pointer adding events with timestamp <= q, and a left pointer removing events with timestamp < q - 300. Keep a per-user message count and a per-(user, chat) record of the latest react or end_chat event in the window. The pitfall is eviction. When a react expires, the chat's state isn't simply gone, an older event might not matter, but a later one must be found. Keep a deque of react/end_chat events per (user, chat), and the latest is the tail. Delete empty entries so memory stays bounded. Active count updates incrementally. Sort user IDs only when formatting each answer. StealthCoder is the hedge if the eviction logic tangles live.
If you see this problem in your OA tomorrow, the play is to recognize the pattern in 30 seconds. StealthCoder buys you that recognition.
You can drill Message Event Aggregation cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Built by an Amazon engineer who passed his OA cold and still thinks the filter is broken.
Get StealthCoderRelated leaked OAs
You've seen the question.
Make sure you actually pass OpenAI's OA.
OpenAI reuses patterns across OAs. Built by an Amazon engineer who passed his OA cold and still thinks the filter is broken. Works on HackerRank, CodeSignal, CoderPad, and Karat.
Message Event Aggregation FAQ
What's the trick in OpenAI's Message Event Aggregation?+
Sort events once, sort queries once, then slide a window with two pointers. Add events as the right edge passes them and remove events as they fall below q - 300. Maintain counts incrementally instead of recomputing per query, which brings it to roughly O((n + m) log n).
How do I track active chats when events expire?+
Store, per user and chat, the in-window react and end_chat events in order. The latest one decides active or not. When the oldest expires, pop it from the front. If the deque empties, delete the key. Update the user's activeChatCount only when the latest state flips.
Why does the memory requirement matter?+
It rules out a global map of every chat ever seen. Remove users and user-chat pairs once nothing remains in the window. A grader may check this, and it also keeps your solution honest about eviction, which is where most bugs hide.
How do I handle out-of-order events and ties?+
Attach the original index to each event and sort by timestamp, then index. Python's sort is stable, so sorting by timestamp alone works. Queries need the same treatment: sort by time with original positions saved, then write results back into the original order.
How should I prepare in 48 hours?+
Write a clean sliding window over sorted data with a deque and a hash map of counts. Then practice eviction that deletes empty keys. Test both examples by hand, including the end_chat at 300 that makes chat c8 inactive, and empty-window queries returning an empty string.