Katkat

Implement a Range Module with Add, Query, and Remove

Walmart LabsSoftware Engineer · Technical Screen

Last updated

Design a data structure that tracks coverage of real numbers using half-open intervals, supporting dynamic additions, removals, and continuous coverage queries. This tests ordered data structure manipulation, interval merging/splitting invariants, and binary search.

Problem Statement

A Range Module tracks ranges of numbers over the real number line using half-open intervals [left, right), which represents all numbers x such that left None: Adds the half-open interval [left, right) to the tracked set. Any overlapping or contiguous intervals must be merged.

  • query_range(left: int, right: int) -> bool: Returns True if every real number in [left, right) is currently tracked, and False otherwise.
  • remove_range(left: int, right: int) -> None: Stops tracking every real number in [left, right). This may split existing intervals into multiple disjoint intervals.

Interface

class RangeTracker:
    def __init__(self):
        pass

    def add_range(self, left: int, right: int) -> None:
        pass

    def query_range(self, left: int, right: int) -> bool:
        pass

    def remove_range(self, left: int, right: int) -> None:
        pass

Examples

Example 1

tracker = RangeTracker()
tracker.add_range(10, 20)
tracker.remove_range(14, 16)

tracker.query_range(10, 14)  # Returns True: [10, 14) is fully tracked
tracker.query_range(13, 15)  # Returns False: 14 to 15 is not tracked
tracker.query_range(16, 17)  # Returns True: [16, 17) is fully inside [16, 20)

tracker.add_range(14, 16)
tracker.query_range(10, 20)  # Returns True: the hole was filled, merging back to [10, 20)

Example 2

tracker = RangeTracker()
tracker.add_range(1, 5)
tracker.add_range(5, 10)
# Contiguous intervals [1, 5) and [5, 10) merge into [1, 10)
tracker.query_range(1, 10)   # Returns True

tracker.remove_range(3, 8)
# [1, 10) is split into [1, 3) and [8, 10)
tracker.query_range(1, 3)    # Returns True
tracker.query_range(3, 4)    # Returns False
tracker.query_range(8, 10)   # Returns True

Constraints

  • 1 <= left < right <= 10^9 for all calls.
  • At most 10^4 total calls will be made to add_range, query_range, and remove_range.

Edge Cases

  • Adding an interval that is completely contained within an existing interval.
  • Removing a range that spans across multiple disjoint intervals, partially cutting off edges on both sides.
  • Adjacent intervals sharing an endpoint (e.g., [1, 5) and [5, 10)) must merge in add_range, but do not require merging across a removed boundary.

Hints

Hint 1

Maintain disjoint, sorted intervals at all times so that binary search can locate overlapping candidates in O(log n) time.

Hint 2

For query_range(left, right), binary search for the unique interval that could contain left; the query returns True if and only if that interval exists and extends to at least right.

Hint 3

For add_range and remove_range, locate the start and end insertion points, collect all affected intervals, and replace that slice with at most one or two updated intervals.

Follow-up questions

  • How would you optimize this design if read queries (query_range) vastly outnumber write operations (add/remove)?
  • How could this data structure be made thread-safe for concurrent read and write operations in a high-throughput microservice?
  • If the coordinate values were floating-point numbers or arbitrarily large timestamps, what changes (if any) would you make to your interval representation?