Katkat

Design a Stateful Working Hours Register

AirbnbSoftware Engineer · Online Assessment

Last updated

Design an in-memory working hours and compensation tracking register that supports badge events, top-k leaderboards, scheduled rate changes, and interval-based historical earnings calculations.

Problem Statement

Design an in-memory system, WorkingHoursRegister, that tracks employee badge events, calculates completed working time, ranks employees by productivity, and deterministically computes historical compensation across delayed pay-rate updates.

Implement the following methods:

  • register_employee(emp_id: str, initial_hourly_rate: int) -> bool Registers a new employee with their starting pay rate (in USD per hour). Returns False if emp_id is already registered; otherwise registers the employee and returns True.

  • badge_in(emp_id: str, timestamp: int) -> bool Badges an employee into work. Returns False if the employee does not exist, is currently badged in, or timestamp is less than or equal to the employee's last recorded badge event timestamp. Otherwise, records the event and returns True.

  • badge_out(emp_id: str, timestamp: int) -> bool Badges an employee out. Returns False if the employee does not exist, is not currently badged in, or timestamp is less than or equal to the corresponding badge_in timestamp. Otherwise, completes the shift [badge_in_time, timestamp] and returns True.

  • get_top_k_employees(k: int) -> list[str] Returns the IDs of the top k employees who have accumulated the highest total completed working seconds across all completed shifts. Active (open) badge-in sessions do not count. If two employees have the exact same total completed time, tie-break lexicographically ascending by emp_id. If fewer than k employees exist, return all registered employees sorted accordingly.

  • schedule_promotion(emp_id: str, effective_time: int, new_hourly_rate: int) -> bool Schedules a pay-rate change for emp_id taking effect at timestamp >= effective_time. Multiple promotions can be scheduled in any order, but no two promotions for the same employee may have the same effective_time. Returns False if the employee is not registered, new_hourly_rate float Computes total earnings for emp_id strictly within the query window [start_time, end_time). Only completed shifts overlapping with [start_time, end_time) are considered. If a shift crosses promotion boundaries or window boundaries, split the shift and apply the respective hourly rate for each sub-interval. Earnings for a sub-interval of duration_seconds at rate dollars/hour equal (duration_seconds / 3600.0) * rate. Return the total earnings rounded to 2 decimal places. If emp_id does not exist or start_time >= end_time, return 0.0.

Example

register = WorkingHoursRegister()
register.register_employee("alice", 36) # $36/hr = $0.01/sec
register.register_employee("bob", 72)   # $72/hr = $0.02/sec

register.badge_in("alice", 0)
register.badge_out("alice", 3600)  # alice works 3600 seconds

register.badge_in("bob", 1000)
register.badge_out("bob", 2800)   # bob works 1800 seconds

# Top 1 employee
register.get_top_k_employees(1)  # ["alice"]

# Schedule a promotion for Alice at t = 5000 to $72/hr
register.schedule_promotion("alice", 5000, 72)

# Alice works across the promotion boundary
register.badge_in("alice", 4000)
register.badge_out("alice", 6000)
# Worked 4000..5000 (1000s at $36/hr = $10.00) + 5000..6000 (1000s at $72/hr = $20.00) = $30.00

# Query Alice earnings for 0..7000:
# Shift 1 (0..3600 at $36/hr): $36.00
# Shift 2 (4000..6000): $30.00
# Total: 66.00
register.calculate_earnings("alice", 0, 7000)  # 66.0

# Query Alice earnings within a restricted window [3000, 5500):
# Shift 1 overlap: [3000, 3600) = 600s @ $36/hr = $6.00
# Shift 2 overlap: [4000, 5000) = 1000s @ $36/hr = $10.00
#                  [5000, 5500) = 500s @ $72/hr = $10.00
# Total: 6.00 + 10.00 + 10.00 = 26.00
register.calculate_earnings("alice", 3000, 5500)  # 26.0

Constraints

  • 0 <= timestamp, start_time, end_time, effective_time <= 10^9
  • Total operations across all methods <= 50,000
  • Maximum number of registered employees <= 1,000
  • 1 <= k <= 1,000
  • 1 <= initial_hourly_rate, new_hourly_rate <= 1,000
  • Badge timestamps for any single employee are strictly increasing across events.

Hints

Hint 1

Maintain each employee's completed work intervals and rate-change timelines in sorted order to allow binary search during earnings calculation.

Hint 2

For top-k queries, maintain total completed hours per employee and update upon each valid badge_out, or sort dynamically if top-k calls are infrequent relative to badge events.

Hint 3

When computing earnings for an interval, intersect the query interval with each completed shift, then slice each resulting interval by any promotion timestamps falling within it.

Follow-up questions

  • How would you optimize `calculate_earnings` if an employee had tens of thousands of short completed shifts?
  • How would you handle out-of-order or backfilled badge corrections (e.g., retroactively fixing a missed badge_out)?
  • How would the system scale if badge events were ingested concurrently from thousands of physical office card readers?