Implement CSAT tracking and call timing
Company: Decagon
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates skills in designing efficient streaming/rolling-window data structures and stateful updates for real-time metrics, alongside parsing and simulating nested function call logs to compute exclusive runtimes.
Rolling CSAT Tracker
Constraints
- 1 <= window_size
- score is an integer in [1, 5]
- conversation_id values passed to record are unique
- record and get_average timestamps are non-decreasing
- update on an evicted or unknown conversation is a no-op
Examples
Input: (3, [['record','conv0',1,1],['record','conv1',5,2],['get_average',3],['record','conv2',4,3],['get_average',4],['record','conv3',3,4],['get_average',4],['record','conv4',1,5],['get_average',5]])
Expected Output: [3.0, 3.33, 3.25, 3.25]
Explanation: At t=3 window is ts>=0 -> [1,5] avg 3.0. At t=4 window ts>=1 -> [1,5,4] avg 3.33, then [1,5,4,3] avg 3.25. record(ts=5) evicts conv0 (ts=1, 5-1>3); at t=5 window ts>=2 -> [5,4,3,1] avg 3.25.
Input: (3, [['record','conv0',1,1],['record','conv1',5,2],['record','conv2',4,3],['record','conv3',3,4],['update','conv2',2],['get_average',4],['record','conv4',1,5],['get_average',5],['update','conv0',5],['get_average',5]])
Expected Output: [2.75, 2.75, 2.75]
Explanation: After updating conv2 4->2 the window [1,5,2,3] averages 2.75. record(ts=5) evicts conv0; window [5,2,3,1] averages 2.75. update('conv0',5) is a no-op because conv0 was already evicted, so the average stays 2.75.
Input: (3, [['get_average',0]])
Expected Output: [None]
Explanation: No records have ever been inserted, so get_average returns None.
Input: (2, [['record','a',5,10],['get_average',10],['get_average',12],['get_average',13]])
Expected Output: [5.0, 5.0, None]
Explanation: Record a at ts=10 with window 2. At t=10 and t=12 it is active (12-10=2, not > 2) -> avg 5.0. At t=13, 13-10=3 > 2 so a is evicted, leaving the window empty -> None.
Input: (5, [['record','x',4,0],['update','x',2],['get_average',0],['update','y',5],['get_average',0]])
Expected Output: [2.0, 2.0]
Explanation: x is recorded with score 4 then updated to 2 -> avg 2.0. update('y',5) targets an id that was never recorded, so it is ignored and the average remains 2.0.
Hints
- Keep a deque of (timestamp, conversation_id) in insertion (eviction) order plus a hash map conversation_id -> score and a running total. Evicting is then popping from the left while timestamp - oldest_ts > window_size.
- update only mutates the score in the map and adjusts the running total by (new - old); it must never touch the deque, so timestamp and eviction order stay intact. If the id is absent from the map it was evicted (or never recorded) — do nothing.
- Maintain total incrementally so get_average is O(1) plus the eviction it triggers; never re-sum the whole window.
Exclusive Time of Functions
Constraints
- 1 <= n
- Each log is 'id:start:ts' or 'id:end:ts' with 0 <= id < n and ts an integer
- logs are sorted by timestamp and form a valid, properly nested trace
- An end at time t includes unit t in the runtime
Examples
Input: (2, ['0:start:0', '1:start:2', '1:end:5', '0:end:6'])
Expected Output: [3, 4]
Explanation: Func 0 runs units 0-1 (2 units) then 6 (1 unit) = 3. Func 1 runs units 2-5 = 4. The classic LeetCode example.
Input: (1, ['0:start:0', '0:start:2', '0:end:5', '0:start:6', '0:end:6', '0:end:7'])
Expected Output: [8]
Explanation: A function recursing into itself. All units 0..7 are charged to function 0: units 0-1 (2), 2-5 (4), 6 (1), 7 (1) = 8.
Input: (2, ['0:start:0', '0:start:2', '0:end:5', '1:start:6', '1:end:6', '0:end:7'])
Expected Output: [7, 1]
Explanation: Function 0 runs units 0-5 and 7 = 7 exclusive units; function 1 runs only unit 6 = 1.
Input: (1, ['0:start:0', '0:end:0'])
Expected Output: [1]
Explanation: Start and end at the same time unit 0: the end includes unit 0, so the runtime is 1.
Input: (3, ['0:start:0', '1:start:2', '2:start:4', '2:end:5', '1:end:6', '0:end:8'])
Expected Output: [4, 3, 2]
Explanation: 0 runs 0-1 (2) and 7-8 (2) = 4; 1 runs 2-3 (2) and 6 (1) = 3; 2 runs 4-5 = 2.
Hints
- Use a stack of currently-running function ids and a variable `prev` for the start of the next chargeable interval. The function on top of the stack is the one actually consuming CPU time.
- On a start at time t: the function currently on top has run for (t - prev) exclusive units, so add that, then push the new id and set prev = t.
- On an end at time t: the popped function ran for (t - prev + 1) units (the +1 because the end unit counts), then set prev = t + 1 since the next unit is the earliest the resumed parent can run.