Product design

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.

01
CODE EXERCISEAdvanced

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.