Design IP/CIDR rule matcher
Company: Databricks
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of IPv4 addressing, CIDR and numeric range representations, plus competency in designing efficient data structures and algorithms for low-latency rule matching with dynamic updates.
Part 1: IPv4 Rule Matcher with Add, Remove, and Query
Constraints
- 0 <= len(operations) <= 100000
- All IPv4 addresses are valid dotted-decimal IPv4 strings
- CIDR prefix lengths are between 0 and 32 inclusive
- Actions are either 'accept' or 'deny'
- For range rules, start and end fit in the IPv4 space; treat the rule as inclusive
Examples
Input: [('add_cidr', '10.0.0.0/8', 'accept'), ('add_cidr', '10.1.0.0/16', 'deny'), ('query', '10.1.2.3'), ('query', '10.2.3.4'), ('query', '11.0.0.1')]
Expected Output: ['deny', 'accept', 'none']
Explanation: The /16 rule is more specific than the /8 rule for 10.1.2.3. The address 10.2.3.4 matches only the /8. The address 11.0.0.1 matches nothing.
Input: [('add_cidr', '192.168.1.1/32', 'deny'), ('add_range', '192.168.1.1', '192.168.1.1', 'accept'), ('query', '192.168.1.1')]
Expected Output: ['accept']
Explanation: Both rules cover exactly one IP, so they have equal specificity. The newer rule wins.
Hints
- Convert every IPv4 address to a 32-bit integer before comparing ranges. Use explicit parentheses when shifting and masking.
- Because the full operation list is known in advance, compress only the IPs that are actually queried, then support range updates and point queries on those coordinates.
Part 2: Choose the Best Rule-Index Data Structure for Firewall Workloads
Constraints
- 1 <= len(scenarios) <= 100000
- All count fields are integers between 0 and 10^9
- coordinate_count is an integer between 0 and 10^9
- Use L(x) = 0 if x <= 1, otherwise ceil(log2(x))
- If multiple strategies have the same cost, use priority: sorted_disjoint_intervals < ordered_map < interval_tree < segment_tree < radix_trie < hybrid(...)
Examples
Input: [{'prefix_rules': 0, 'range_rules': 8, 'prefix_updates': 0, 'range_updates': 0, 'queries': 100, 'disjoint_ranges': True, 'coordinate_count': 0}]
Expected Output: ['sorted_disjoint_intervals']
Explanation: This is a pure static disjoint-range workload, which is exactly what sorted disjoint intervals are best at under the given model.
Input: [{'prefix_rules': 10, 'range_rules': 0, 'prefix_updates': 5, 'range_updates': 0, 'queries': 20, 'disjoint_ranges': False, 'coordinate_count': 0}]
Expected Output: ['radix_trie']
Explanation: This is a pure prefix workload, and only the radix trie directly targets prefixes without converting them to ranges.
Hints
- First classify each scenario as pure range, pure prefix, mixed, or empty. Different strategy sets are valid in different categories.
- Avoid floating-point logs. In Python, ceil(log2(x)) for x > 1 can be computed as (x - 1).bit_length().