The Number Miner

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
L3
Asked in
Equifax

Problem

You're auditing a batch of JSON event payloads whose objects and arrays nest arbitrarily deep, and you need a tally of the integers buried inside them. Count how many times each genuine integer value appears anywhere across every payload, returning a map from the integer's string form to its total, where floats and booleans never count, not even a whole-looking 100.0 or a bare true.

JSON strings are hiding numeric secrets. Dig them out.

Example 1

Input:

json_strings = ["{\"user\": {\"age\": 25, \"score\": 100}, \"level\": 3}","{\"user\": {\"age\": 25}, \"level\": 3}"]

Output:

{"3":2,"25":2,"100":1}

Example 2

Input:

json_strings = ["{\"temp\": 72, \"readings\": [5, 5, 3.5], \"active\": true}"]

Output:

{"5":2,"72":1}

Worked solution and explanation

What this problem really is

Strip the JSON costume and this is a recursive walk that counts integers, with a type filter that has to be exact on two fronts. The first trap is depth: iterate only each object's top-level values and every integer sitting inside a nested object or array is invisible to you, so your totals come out quietly too low. The second trap is bool: in Python bool subclasses int, so isinstance(True, int) is True. Filter with isinstance(val, int) alone and every true/false in a payload lands in your counts, inflating them in a way that still looks plausible. Floats are the mirror case: 100.0 must not count even though it looks whole.


How to get there

Step 1: Parse each payload

Use json.loads to turn each string into a native Python value: a dict, a list, or a scalar. Native types are exactly what let the filter tell an int from a float from a bool downstream.

Step 2: Recurse into objects and arrays

Walk the parsed value recursively. When you hit a dict, descend into its values; when you hit a list, descend into its items; otherwise you are at a leaf. This is what reaches the integers buried below the top level, which a flat pass over obj.values() would skip entirely.

Step 3: Keep genuine integers, count, and stringify

At each leaf, keep it only if isinstance(val, int) and not isinstance(val, bool): the bool half is not optional, it is what stops True/False from being tallied, and floats fail the int check on their own. Count each survivor, then stringify the keys for the final map.


The solution

Recursive walk with strict integer counting
import json


def count_json_numbers(json_strings):
    counts = {}

    def walk(node):
        if isinstance(node, dict):
            for value in node.values():
                walk(value)
        elif isinstance(node, list):
            for item in node:
                walk(item)
        elif isinstance(node, int) and not isinstance(node, bool):
            counts[node] = counts.get(node, 0) + 1

    for payload in json_strings:
        walk(json.loads(payload))
    return {str(num): count for num, count in counts.items()}

The recursion reaches buried integers; the bool guard keeps true/false out of the tally.

Flat pass (wrong)
for val in obj.values() only sees the top level. On {"user": {"age": 25, "score": 100}, "level": 3} it counts 3 and stops, silently dropping 25 and 100 because they live one level down.
Recursive walk (correct)
Descending into every dict value and list item reaches age, score, and level alike, so 25, 100, and 3 all land in the tally, which is why the expected output counts the nested values.

Common follow-up questions