The Fuller Copy

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
mid

Problem

A scraping pipeline pulls product listings from several sites, and each raw record differs by source: a price may sit under the first entry of offers or as a top-level field, and it arrives as a string like "$1,299.00", while the id and title hide behind different key names. Flatten each raw record into a uniform listing, coercing prices to floats and timestamps to a plain date, and leaving any field that is absent as None. The same listing can be scraped from more than one page under the same id, so when an id repeats, keep the copy with the fewest missing fields.

The same listing lands twice, half-filled each time. Keep what is whole.

Example 1

Input:

records = [{"id":"P100","name":"Aeron Chair","offers":[{"price":"$1,299.00","priceCurrency":"USD"}],"datePosted":"2026-07-01T12:00:00Z","source_site":"chairhub"},{"id":"P200","price":"$499.50","title":"Standing Desk","datePosted":"2026-06-15T08:30:00Z","source_site":"deskmart"},{"sku":"P100","price":1299,"title":"Aeron Chair","source_site":"chairhub"}]

Output:

[{"id":"P100","price":1299,"title":"Aeron Chair","currency":"USD","posted_at":"2026-07-01","source_site":"chairhub"},{"id":"P200","price":499.5,"title":"Standing Desk","currency":null,"posted_at":"2026-06-15","source_site":"deskmart"}]

Example 2

Input:

records = [{"id":"P300","name":"Desk Lamp","source_site":"lightco"}]

Output:

[{"id":"P300","price":null,"title":"Desk Lamp","currency":null,"posted_at":null,"source_site":"lightco"}]

Worked solution and explanation

Why this problem exists in real interviews

This is defensive extraction from heterogeneous records with a merge-on-conflict, dressed up as scraper normalization. The skill being probed: can you pull the same logical field out of records where it lives at a different path and under a different key from every source, coerce its type, and then collapse duplicates by choosing the copy that carries the most data? Anyone can write a dict comprehension. The two traps are (1) record['offers'][0]['price'] blows up with KeyError or IndexError the instant a source omits offers, and (2) a dict keyed by id that overwrites on every hit keeps whichever copy was scraped last, so it silently throws away the price and date the other copy had.


Break down the requirements

Step 1: Extract every field through a fallback chain

The id is under id on one site and sku on another; the title is title or name. Write one helper that walks a list of candidate keys and returns the first non-null value, so adding a new source's key name is a one-line change, not a rewrite.

Step 2: Reach into offers defensively, then fall back

The price prefers offers[0].price and falls back to a top-level price. offers is sometimes absent and sometimes an empty list, so guard with isinstance(offers, list) and offers before indexing [0]. Never index a path you have not proven exists.

Step 3: Coerce types, treat missing as None

Prices arrive as "$1,299.00", "1,050", or the int 1299. Strip the currency symbol and thousands separators, then float it; pass ints straight through float(). A timestamp like 2026-07-01T12:00:00Z becomes the date portion before the T. Anything absent stays None rather than crashing.

Step 4: Deduplicate by keeping the fullest copy

Group by the canonical id. When an id repeats, keep the flat record with the most non-null fields. Replace the stored copy only on a strict improvement, which keeps the first-seen copy on a tie and preserves first-appearance ordering for the output.


The solution

Defensive flatten plus completeness-based dedup
def normalize_listings(records):
    def first_present(rec, keys):
        for k in keys:
            val = rec.get(k)
            if val is not None:
                return val
        return None

    def parse_price(raw):
        if raw is None:
            return None
        if isinstance(raw, (int, float)):
            return float(raw)
        cleaned = raw.replace('$', '').replace(',', '').strip()
        return float(cleaned) if cleaned else None

    def parse_date(raw):
        return raw.split('T')[0] if raw else None

    def flatten(rec):
        offers = rec.get('offers')
        offer = offers[0] if isinstance(offers, list) and offers else {}
        price_raw = offer.get('price')
        if price_raw is None:
            price_raw = rec.get('price')
        return {
            'id': first_present(rec, ['id', 'sku']),
            'title': first_present(rec, ['title', 'name']),
            'price': parse_price(price_raw),
            'currency': offer.get('priceCurrency'),
            'posted_at': parse_date(first_present(rec, ['datePosted', 'posted_at'])),
            'source_site': rec.get('source_site'),
        }

    def completeness(flat):
        return sum(1 for v in flat.values() if v is not None)

    best = {}
    order = []
    for rec in records:
        flat = flatten(rec)
        cid = flat['id']
        if cid not in best:
            best[cid] = flat
            order.append(cid)
        elif completeness(flat) > completeness(best[cid]):
            best[cid] = flat
    return [best[cid] for cid in order]

Naive
out = {} for r in records: out[r['id']] = { 'price': float(r['offers'][0]['price']), ... } Crashes on a missing 'id' key, a source that uses 'sku', an absent 'offers', a price string with a '$', and quietly drops the fuller copy on the second write.
Correct
Pull each field through a key-fallback helper, guard the offers index with isinstance, coerce the price and date, and replace a stored id only when the new copy has strictly more non-null fields. Resilient to new key names and missing paths, and it keeps the most complete record.

Common follow-up questions