Q4 - Reading the App Store Metrics Feed
We believe variations of this interview question have recently been asked by Apple to software engineering candidates. It looks like a five-minute string-splitting exercise, and then the values bite: the App Store reports numbers in its abbreviated dashboard form (1.2K, 3.4M, 2B), and turning those into exact totals, without the floating-point rounding that trips most people up, is the real work.
Scenario: App Store Connect hands you a plain-text metrics feed, one reading per line. Each line names an app, a metric, and a value: app,metric,value. The catch is the value. It uses the App Store's abbreviated display format, a number with an optional K (thousand), M (million), or B (billion) suffix, so 890, 1.2K, 3.4M, and 2B are all valid. The same app and metric can appear on many lines, one reading per day, and those readings add up.
🧩 The format is left vague on purpose, so ask. You get a few example lines and are trusted to fill in the rest, exactly like a real Apple round. Before writing any code, pin it down out loud: can a value be negative, like a refund? Can the number carry more than one decimal place (1.25K)? Are metric names case-sensitive? Could an app name contain a comma? What type should a total be? Apple interviewers leave these open on purpose and watch whether you notice, so state your assumptions and move.
Here is how a few values parse, including the one that catches people:
How the values parse
Value: "2B" "1.005K" "12.5M" "950"
Parses to: 2,000,000,000 1,005 12,500,000 950
The trap: "1.005K" is exactly 1005. The tempting float("1.005") * 1000
comes out as 1004.9999..., which truncates to 1004, off by one.
Parse with integers and it is exact.
Part 1: Given the feed and a target metric (say downloads), return every app that reported that metric, ranked by its total for that metric, highest first, ties broken alphabetically by app name.
Example 1
Input: metric = "downloads"
feed =
Music,downloads,1.2K
Podcasts,downloads,890
Music,downloads,2.1K
News,downloads,3.4M
Podcasts,downloads,1.1K
Output: [ ("News", 3400000), ("Music", 3300), ("Podcasts", 1990) ]
Why: Music = 1.2K + 2.1K = 1200 + 2100 = 3300. Podcasts = 890 + 1.1K = 1990.
News = 3.4M = 3,400,000. Ranked from the highest total down.
Part 1: Parse Exactly, Then Roll Up
There are two jobs here, and the first one is the trap.
Parse a value without touching a float. The obvious move, float("1.2") * 1000, works right up until it doesn't: float("1.005") is really 1.00499999..., so float("1.005K") lands on 1004 instead of 1005. Keep everything in integers. Strip the suffix to get a multiplier (K is 1000, M is a million, B a billion, no suffix is 1). Drop the decimal point to read all the digits as one whole number, and remember how many decimal places there were. Then the value is:
digits * (multiplier / 10^decimals)
Because the multiplier is a power of ten no smaller than 10^decimals, that inner division is exact, an integer, so a float never enters the picture. For 1.2K: digits 12, multiplier 1000, one decimal, so 12 * (1000 / 10) = 12 * 100 = 1200.
Roll up. Keep a map from app name to its running total. Walk the feed once, and for each line whose metric matches the target, add the parsed value to that app's total. Then sort the entries by total descending, breaking ties on the app name. That map is the entire data model.
The float-free parse comes down to two lines: drop the dot to read every digit, then scale by an exact integer division.
digits = int(value_str.replace(".", "")) # "1.005" -> 1005 (drop the dot)
return digits * (multiplier // 10**decimals) # 1005 * (1000 // 1000) = 1005, exact, no float
End to end on Example 1, the parse feeds the roll-up, which feeds the ranking:
metric = "downloads", parse each value with integers, then sum per app:
Music 1.2K -> 1200
Podcasts 890 -> 890
Music 2.1K -> 2100
News 3.4M -> 3,400,000
Podcasts 1.1K -> 1100
totals: Music 3300, Podcasts 1990, News 3,400,000
rank highest first (ties by name): News, Music, Podcasts
# parse: O(len) | roll up + rank: O(n log n) for n matching apps
def parse_value(value_str):
value_str = value_str.strip()
multiplier = 1
# a K/M/B suffix is the scale: "1.2K" -> multiplier 1000, then value_str becomes "1.2"
if value_str[-1] in "KMB":
multiplier = {"K": 10**3, "M": 10**6, "B": 10**9}[value_str[-1]]
value_str = value_str[:-1]
# "1.2" -> 1 digit after the dot
decimals = len(value_str) - value_str.index(".") - 1 if "." in value_str else 0
# drop the dot: "1.2" -> 12
digits = int(value_str.replace(".", ""))
# 12 * (1000 / 10) = 1200; "1.005K" stays 1005, not 1004
return digits * (multiplier // 10**decimals)
def rank_by_metric(lines, metric):
# app name -> its running total for this metric
totals = {}
for line in lines:
app, line_metric, value_str = [t.strip() for t in line.split(",")]
if line_metric == metric:
# "Music,downloads,1.2K" adds 1200 to Music
totals[app] = totals.get(app, 0) + parse_value(value_str)
# rank highest total first, ties broken by app name (A before B)
return sorted(totals.items(), key=lambda kv: (-kv[1], kv[0]))
Part 2: Put It Back in the Dashboard's Format
The App Store does not show 3400000 to a user; the dashboard shows 3.4M. Return the same ranking, but with each total rendered the App Store way: the largest suffix that fits, at most one decimal place, and no trailing .0. So 3400000 becomes 3.4M, 3300 becomes 3.3K, 12000 becomes 12K, 2000000000 becomes 2B, and anything under a thousand stays plain (950).
Keep the same integer discipline. To read off the single decimal digit, compute value * 10 / unit with integer division and take it mod 10. No float, no surprise rounding. This version truncates to one decimal (so 1990 in thousands is 1.99K, shown as 1.9K); whether the App Store rounds or truncates is a fine thing to confirm with your interviewer.
Rendering back is the same integer discipline in reverse:
render each total the App Store way (biggest suffix, one decimal, no trailing .0):
3,400,000 -> 3.4M (3,400,000 // 1,000,000 = 3, tenths = 4)
3300 -> 3.3K
1990 -> 1.9K (1.99K truncated to one decimal, not rounded)
950 -> 950 (under 1000 stays plain)
Example 2
Input: the ranking from Part 1
Output: [ ("News", "3.4M"), ("Music", "3.3K"), ("Podcasts", "1.9K") ]
Why: 3,400,000 becomes "3.4M". 3300 becomes "3.3K".
1990 is 1.99K, and we keep one decimal, so "1.9K".
def format_value(value):
# biggest unit that fits: 3,400,000 -> M
for suffix, unit in (("B", 10**9), ("M", 10**6), ("K", 10**3)):
if value >= unit:
# 3,400,000 / 1,000,000 = 3
whole = value // unit
# one decimal via integer math: 34 % 10 = 4
tenths = (value * 10 // unit) % 10
# "3.4M"; a 0 digit is dropped (12000 -> "12K")
return f"{whole}.{tenths}{suffix}" if tenths else f"{whole}{suffix}"
# under 1,000 stays plain: 950 -> "950"
return str(value)
Complexity Analysis
parseValue:O(L)for a value string of lengthL(a couple of passes over a short string), and it allocates nothing meaningful.rankByMetric:O(n + a log a)fornfeed lines andadistinct apps: one pass to total, then a sort of the apps.formatValue:O(1), a constant number of integer operations.- Space:
O(a)for the per-app totals map.
Final Notes
We believe the "App Store metrics feed" problem, and close variations of it, have appeared frequently in recent interviews for software engineers at Apple. It tests something the polished algorithm questions do not: whether you can take a messy, half-specified real-world format, ask the right questions about it, and parse it exactly, which is most of the actual job.
- The float is the trap.
float("1.005") * 1000is1004.999..., so the naive parser is silently off by one on real inputs. Reading the digits as one integer and scaling bymultiplier / 10^decimalskeeps every total exact. Saying "I will avoid floating point here" out loud is the signal. - A map is the whole model. Group by app, sum as you go, sort at the end. Nothing fancier is needed, and reaching for that structure quickly is what the "roll it up" part is really checking.
- Parsing in, formatting out, same discipline. Going from
3400000back to3.4Muses the exact same integer arithmetic,value * 10 / unitthenmod 10for the one decimal. Round versus truncate is a real choice, so name it rather than guessing. - Ask before you parse. Negatives, extra decimals, commas in names, case sensitivity: the prompt leaves these blank on purpose. The candidates who do well settle them in the first two minutes and write the assumptions down.
The reason a parsing question like this shows up at Apple is that real code spends far more time reading other people's formats than inventing clever algorithms. Get comfortable turning a vague spec into precise, integer-exact parsing, and you are showing the kind of care that reads as production-ready. 🚀