Katkat

Query Order and Batch Lifetimes from Event Files

Hudson River TradingSoftware Engineer · Technical Screen

Last updated

Design an event-driven tracker to query lifetimes for individual orders or order batches, correctly handling open orders and aggregate start/end timestamps.

Problem Statement

In trading systems, orders are often submitted individually or grouped into logical execution batches. You are given a stream of chronological order lifecycle events from an event log. Each event records a timestamp, an action ("CREATE" or "CLOSE"), an order_id, and an optional batch_id (present on "CREATE").

Implement the OrderLifetimeTracker class with the following methods:

  • process_event(timestamp: int, action: str, order_id: str, batch_id: str | None = None) -> None: Ingests an event. Actions are either "CREATE" (which opens the order at timestamp and associates it with batch_id if provided) or "CLOSE" (which marks the order as finished at timestamp).
  • get_lifetime(identifier: str) -> tuple[int, int] | None: Queries the lifetime (start_time, end_time) for a given identifier:
    • If identifier is an order_id:
      • start_time is the timestamp when the order was created.
      • end_time is the timestamp when the order closed, or 0 if it is still open.
    • If identifier is a batch_id:
      • start_time is the earliest creation timestamp among all orders in the batch.
      • end_time is the latest close timestamp among all orders in the batch. However, if any order in the batch is still open, end_time must be 0.
    • If identifier matches neither an existing order nor a batch, return None.

You may assume all order_id and batch_id values come from disjoint sets (no ID is used as both an order and a batch).

Interface

class OrderLifetimeTracker:
    def __init__(self):
        pass

    def process_event(self, timestamp: int, action: str, order_id: str, batch_id: str | None = None) -> None:
        pass

    def get_lifetime(self, identifier: str) -> tuple[int, int] | None:
        pass

Constraints

  • Timestamps are positive integers strictly increasing or non-decreasing: 1 <= timestamp <= 10^9.
  • Each order_id is created at most once and closed at most once.
  • An order is never closed before it is created.
  • Total number of events and queries N <= 200,000.
  • get_lifetime should execute in O(1) time.

Example

tracker = OrderLifetimeTracker()

# Order 1 and Order 2 belong to Batch B1
tracker.process_event(100, "CREATE", "O1", "B1")
tracker.process_event(105, "CREATE", "O2", "B1")

# Order 3 is standalone (no batch)
tracker.process_event(110, "CREATE", "O3", None)

# Close O1
tracker.process_event(150, "CLOSE", "O1")

tracker.get_lifetime("O1")  # returns (100, 150)
tracker.get_lifetime("O2")  # returns (105, 0) -> still open
tracker.get_lifetime("B1")  # returns (100, 0) -> O2 is still open, so end is 0
tracker.get_lifetime("O3")  # returns (110, 0)

# Close O2
tracker.process_event(180, "CLOSE", "O2")
tracker.get_lifetime("B1")  # returns (100, 180) -> all orders closed
tracker.get_lifetime("UNKNOWN")  # returns None

Hints

Hint 1

Maintain state for each order (start time, end time, batch reference) and aggregate metadata for each batch (min start time, max end time, count of total orders, count of open orders).

Hint 2

When an order closes, decrement the batch's open order count and update its maximum end timestamp.

Hint 3

Ensure `get_lifetime` can check whether an ID belongs to an order or a batch in O(1) time without re-scanning all orders in the batch.

Follow-up questions

  • How would you handle out-of-order event delivery (e.g., CLOSE arrives before CREATE over network partitions)?
  • What changes would be required if `batch_id` namespaces and `order_id` namespaces overlapped?
  • If memory is constrained and there are millions of batches, how would you design an eviction or compaction strategy for fully closed batches?