Attio: automate customer follow-up and make every step inspectable
Attio organizes companies, people and deal progress into customer records, then automates updates, assignment and notifications. Run history helps teams trace what one follow-up did and where it stopped.
Break down the problem and try your own design and implementation first. Then explore the reasoning to check states, edge cases, trade-offs, and ways to test your approach. The guide offers a reasoning path, not a complete implementation.
How do you merge duplicate, out-of-order attempt events?
Scenario
Design an event reducer for your own run-log page. Events arrive out of order and may be delivered repeatedly. Produce a stable view of each step in each attempt. Only process an in-memory array of received events: do not send notifications, control execution or implement storage. This is not Attio’s internal protocol.
Input, rules and output
- Input is an array of at most 100,000 non-null objects. Each contains nonempty strings eventId, runId and stepId; positive safe integers attempt and seq; and status equal to running, succeeded or failed. Ignore other fields. Let L be total string length.
- Repeated eventId values are duplicate deliveries only if all listed fields agree exactly; count them once. Any disagreement fails the batch and identifies both array indices. Compare original strings without trimming or case folding.
- Group by (runId, attempt, stepId). seq orders events only within its group. Different eventIds may share seq across groups, but the same seq within one group is a conflict even when statuses agree. Do not simply join IDs with a delimiter, because IDs may contain it.
- A valid complete group has zero or more running events followed by at most one terminal event, succeeded or failed. Nothing may follow a terminal event. A terminal-only group is valid because earlier events may not have arrived. Gaps in seq are valid. Do not infer unseen steps or attempts; seeing only B does not establish that A failed.
- Success returns each group’s maximum seq, its status and unique event count, plus the total unique count across groups. Sort deterministically by runId, attempt and stepId. Strings use Unicode code-point lexicographic order, not locale collation. Failure returns no partial result and never mutates inputs. One sufficiently located error is enough; error precedence need not be fixed.
Start with this example
Receive, in order: (e2,R,1,A,2,succeeded), (e1,R,1,A,1,running), an identical duplicate of e2, then (e3,R,2,A,1,running). Write both attempt summaries and unique counts. Independently append: (a) (e4,R,1,A,3,running); (b) another eventId e2 with status changed to failed; (c) (e5,R,1,A,2,succeeded). Explain each conflict and location.
Deliverables
Describe validation, deduplication, grouped sorting, state reduction and all-or-nothing output. Trace the example and list at least four invariants. Analyze n events, L string length and g groups, distinguishing comparisons from string-comparison costs. Discuss JS Map or Rust collections if useful, but provide neither a full implementation nor line-by-line pseudocode.
Explore the reasoning
- Validate fields and index eventId. Keep the first input index. Compare every listed field for repeated IDs: ignore identical duplicates; locate both sides of conflicts. Unknown fields do not affect equality. Validate safe integers and statuses before grouping.
- Isolate attempts with structured keys. Nested maps or tuple keys avoid delimiter collisions. Collect unique events per group and sort by numeric seq. Equal adjacent seq values identify a conflict even when statuses match; they are not duplicate deliveries.
- Check terminal ordering. Repeated running states are valid. Any event after a terminal state fails. A terminal-only group or sequence gap is not an error here. Arrival order is not execution order, and attempt one’s success cannot overwrite attempt two.
- Trace the example. R/1/A ends at seq2, succeeded, with two unique events. R/2/A ends at seq1, running, with one; total three. Variant a violates the terminal rule, b conflicts under one eventId, and c conflicts at one group/seq. Every failure withholds partial summaries.
- Check determinism and cost. Reordering input or adding identical duplicates preserves successful summaries; attempts stay separate; a group has at most one terminal event; failure preserves inputs. Group sorting uses at most O(n log n) numeric comparisons; summary sorting uses O(g log g) key comparisons. Hashing includes L, and string comparisons can scan common prefixes. JS must use code points rather than default UTF-16 order. Indexes and event references use O(n+L) scale space without copying unrelated data.