The Online Elite

A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
medium
Seniority
L4
Asked in
Visa

Problem

A payments platform logs every transaction as online or offline and ties each to a merchant. Given the merchants and the payments, return the average of all online transaction amounts belonging to the top_n merchants by total online volume, or 0.0 when no online transactions qualify.

The top performers are hiding in the data.

Example

Input:

merchants = [{"name":"ShopA","merchant_id":1},{"name":"ShopB","merchant_id":2},{"name":"ShopC","merchant_id":3}], payments = [{"mode":"online","amount":100,"merchant_id":1},{"mode":"online","amount":200,"merchant_id":1},{"mode":"online","amount":50,"merchant_id":2},{"mode":"offline","amount":50,"merchant_id":2},{"mode":"online","amount":400,"merchant_id":3}], top_n = 2

Output:

233.33333333333334

Worked solution and explanation

What this problem really is

Underneath the merchant-analytics story this is a two-grain aggregation, and the grain switch is the whole game. You total online amounts per merchant to decide who the top_n are, then you throw those totals away and average the raw online transactions of only those merchants. A plain group-by hands you the totals; the trap is finishing the job with them. Average the per-merchant totals (350) instead of the individual payments (233.33) and you ship a number that looks reasonable and is quietly wrong, and forget the empty case and the platform 500s on a ZeroDivisionError the first quiet day with no online sales.


The two grains

Hold both grains at once. The merchant-level total is scaffolding: it exists only to rank and select. The value you return lives at the transaction level, scoped to the merchants that survived the cut. Mix them up and every requirement after step 3 goes wrong.

Step 1: Filter to online transactions

Keep only payments where mode == 'online'. Everything downstream reads from this filtered list, so offline rows never touch the total or the average.

Step 2: Sum online volume per merchant

Each payment already carries its merchant_id, so a single pass into a dict keyed by that id gives the per-merchant online total in O(t). Record the index where each merchant first appears so ties can be resolved later.

Step 3: Rank and cut to top_n

Sort the merchant ids by a (-total, first_seen) tuple key so the biggest totals come first and equal totals keep the order their online payments first appeared, then slice the first top_n. The explicit first-appearance term keeps ties from drifting to id order.

Step 4: Average the transactions of the survivors

Scan the online transactions once more, keep those whose merchant is in the top set, and return the mean of their amounts (0.0 if the set is empty).


The solution

Filter, sum, rank, then average
def top_online_merchant_avg(merchants, payments, top_n):
    online = [p for p in payments if p.get('mode') == 'online']
    volume = {}
    first_seen = {}
    for i, p in enumerate(online):
        mid = p['merchant_id']
        if mid in volume:
            volume[mid] += p['amount']
        else:
            volume[mid] = p['amount']
            first_seen[mid] = i
    ranked = sorted(volume, key=lambda mid: (-volume[mid], first_seen[mid]))
    top_ids = set(ranked[:top_n])
    amounts = [p['amount'] for p in online if p['merchant_id'] in top_ids]
    if amounts:
        return sum(amounts) / len(amounts)
    else:
        return 0.0

Common follow-up questions