Design a coupon pricing engine
Company: Applied Intuition
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates software design and algorithmic problem-solving skills focused on pricing logic, including handling financial rounding, discount ordering, eligibility criteria across item categories, and time/space complexity analysis.
Part 1: Apply One Coupon to a Single Category
Constraints
- 0 <= len(items) <= 200000
- 0 <= unit_price <= 10^9
- 0 <= minimum_total_item_requirement <= len(items)
- 0 <= minimum_total_price_requirement <= 10^14
- 0 <= percentage_discount <= 100
- 0 <= value_discount <= 10^14
- All prices are in cents.
Examples
Input: ([{'id': 1, 'category': 'A', 'unit_price': 1000}, {'id': 2, 'category': 'A', 'unit_price': 2000}, {'id': 3, 'category': 'B', 'unit_price': 500}], {'applicable_category': 'A', 'minimum_total_item_requirement': 2, 'minimum_total_price_requirement': 2500, 'percentage_discount': 10, 'value_discount': 300})
Expected Output: 2900
Explanation: Eligible A subtotal is 3000. Apply 10% off => 300 cents discount, then 300 cents flat discount, so A items become 2400. Add B item 500 => 2900.
Input: ([{'id': 1, 'category': 'A', 'unit_price': 999}], {'applicable_category': 'A', 'minimum_total_item_requirement': 1, 'minimum_total_price_requirement': 0, 'percentage_discount': 15, 'value_discount': 0})
Expected Output: 849
Explanation: 15% of 999 is 149.85 cents, which rounds half up to 150 cents. Final total is 999 - 150 = 849.
Input: ([{'id': 1, 'category': 'A', 'unit_price': 1000}, {'id': 2, 'category': 'B', 'unit_price': 500}], {'applicable_category': 'A', 'minimum_total_item_requirement': 2, 'minimum_total_price_requirement': 1000, 'percentage_discount': 20, 'value_discount': 100})
Expected Output: 1500
Explanation: Only one eligible A item exists, so the minimum item requirement is not met. No discount is applied.
Input: ([{'id': 1, 'category': 'A', 'unit_price': 500}, {'id': 2, 'category': 'B', 'unit_price': 700}], {'applicable_category': 'A', 'minimum_total_item_requirement': 1, 'minimum_total_price_requirement': 0, 'percentage_discount': 50, 'value_discount': 400})
Expected Output: 700
Explanation: Eligible subtotal is 500. After 50% off, it becomes 250. Subtract 400 more, clamp at 0. Final total is only the B item: 700.
Input: ([], {'applicable_category': 'A', 'minimum_total_item_requirement': 1, 'minimum_total_price_requirement': 0, 'percentage_discount': 10, 'value_discount': 100})
Expected Output: 0
Explanation: Empty cart edge case.
Solution
def solution(items, coupon):
total = 0
eligible_total = 0
eligible_count = 0
target_category = coupon['applicable_category']
for item in items:
price = item['unit_price']
total += price
if item['category'] == target_category:
eligible_total += price
eligible_count += 1
if eligible_count < coupon['minimum_total_item_requirement']:
return total
if eligible_total < coupon['minimum_total_price_requirement']:
return total
percent = coupon['percentage_discount']
value_discount = coupon['value_discount']
percent_discount_amount = (eligible_total * percent + 50) // 100
discounted_eligible_total = eligible_total - percent_discount_amount - value_discount
if discounted_eligible_total < 0:
discounted_eligible_total = 0
return total - eligible_total + discounted_eligible_total
Time complexity: O(n), where n is the number of items.. Space complexity: O(1), excluding the input..
Hints
- Compute the full cart total and the eligible subtotal separately.
- For half-up rounding in cents, the percentage discount on subtotal S with percent P can be computed as (S * P + 50) // 100.
Part 2: Multi-Category Coupon Engine with Overlapping Coupons
Constraints
- 0 <= len(items) <= 200000
- 0 <= len(coupons) <= 200000
- 1 <= total number of category names across all coupons <= 200000, for non-empty coupon lists
- 0 <= unit_price <= 10^9
- 0 <= minimum_total_item_requirement <= len(items)
- 0 <= minimum_total_price_requirement <= 10^14
- 0 <= percentage_discount <= 100
- 0 <= value_discount <= 10^14
- All prices are in cents.
Examples
Input: ([{'id': 1, 'category': 'grocery', 'unit_price': 500}, {'id': 2, 'category': 'beauty', 'unit_price': 300}, {'id': 3, 'category': 'toy', 'unit_price': 200}], [{'applicable_categories': ['grocery', 'beauty'], 'minimum_total_item_requirement': 2, 'minimum_total_price_requirement': 700, 'percentage_discount': 10, 'value_discount': 100}])
Expected Output: 820
Explanation: The coupon combines grocery and beauty items for subtotal 800. After 10% off and then 100 cents flat off, they become 620. Add toy item 200 => 820.
Input: ([{'id': 1, 'category': 'A', 'unit_price': 1000}, {'id': 2, 'category': 'B', 'unit_price': 600}, {'id': 3, 'category': 'B', 'unit_price': 400}, {'id': 4, 'category': 'C', 'unit_price': 500}], [{'applicable_categories': ['A', 'B'], 'minimum_total_item_requirement': 3, 'minimum_total_price_requirement': 1800, 'percentage_discount': 10, 'value_discount': 100}, {'applicable_categories': ['B', 'C'], 'minimum_total_item_requirement': 1, 'minimum_total_price_requirement': 0, 'percentage_discount': 20, 'value_discount': 0}])
Expected Output: 2100
Explanation: First coupon uses all remaining A and B items: 2000 -> 1700 after discounts. Those items are consumed. Second coupon can only use C, so 500 -> 400. Final total is 1700 + 400 = 2100.
Input: ([{'id': 1, 'category': 'A', 'unit_price': 400}, {'id': 2, 'category': 'B', 'unit_price': 400}], [{'applicable_categories': ['A', 'B'], 'minimum_total_item_requirement': 3, 'minimum_total_price_requirement': 0, 'percentage_discount': 50, 'value_discount': 0}, {'applicable_categories': ['A'], 'minimum_total_item_requirement': 1, 'minimum_total_price_requirement': 0, 'percentage_discount': 25, 'value_discount': 0}])
Expected Output: 700
Explanation: The first coupon is skipped because it needs 3 eligible items but only 2 are available. The second coupon then discounts category A from 400 to 300. Category B stays 400.
Input: ([{'id': 1, 'category': 'A', 'unit_price': 300}, {'id': 2, 'category': 'B', 'unit_price': 200}], [{'applicable_categories': ['A', 'B', 'A'], 'minimum_total_item_requirement': 2, 'minimum_total_price_requirement': 0, 'percentage_discount': 50, 'value_discount': 400}])
Expected Output: 0
Explanation: Duplicate 'A' should not double count. Eligible subtotal is 500, then 50% off gives 250, then subtract 400 and clamp at 0.
Input: ([{'id': 1, 'category': 'X', 'unit_price': 500}], [{'applicable_categories': ['A', 'B'], 'minimum_total_item_requirement': 1, 'minimum_total_price_requirement': 0, 'percentage_discount': 10, 'value_discount': 100}])
Expected Output: 500
Explanation: No eligible items match the coupon categories, so the cart total is unchanged.
Solution
def solution(items, coupons):
from collections import defaultdict
category_count = defaultdict(int)
category_total = defaultdict(int)
remaining_total = 0
for item in items:
category = item['category']
price = item['unit_price']
category_count[category] += 1
category_total[category] += price
remaining_total += price
for coupon in coupons:
categories = set(coupon['applicable_categories'])
eligible_count = 0
eligible_total = 0
for category in categories:
eligible_count += category_count.get(category, 0)
eligible_total += category_total.get(category, 0)
if eligible_count < coupon['minimum_total_item_requirement']:
continue
if eligible_total < coupon['minimum_total_price_requirement']:
continue
percent = coupon['percentage_discount']
value_discount = coupon['value_discount']
percent_discount_amount = (eligible_total * percent + 50) // 100
discounted_total = eligible_total - percent_discount_amount - value_discount
if discounted_total < 0:
discounted_total = 0
remaining_total = remaining_total - eligible_total + discounted_total
for category in categories:
category_count[category] = 0
category_total[category] = 0
return remaining_total
Time complexity: O(n + t), where n is the number of items and t is the total number of category names mentioned across all coupons.. Space complexity: O(k), where k is the number of distinct categories present in the cart or coupons..
Hints
- Aggregate the remaining item count and subtotal by category so each coupon can be evaluated without rescanning every item.
- When a coupon succeeds, all remaining items in its listed categories are consumed, so their category aggregates can be zeroed out.