Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.
How Do You Name a Stranger Before the Phone Has Finished Ringing?
It is 10:41 on a Tuesday morning in a village outside Gorakhpur. Sunita Yadav, 61, a retired anganwadi supervisor, is drying dal on the roof, where her phone shows one bar of signal on a good day. It rings. The number starts with +91 and isn't one she has saved. A man on the other end, if she answered, would say he is calling from "the telecom department" and that her SIM will be cut off in two hours unless she presses 9.
The number didn't exist as a problem yesterday. It started calling at 9:12 this morning, working down a list. By 10:30, hundreds of people across eastern Uttar Pradesh and Bihar had declined it, and some of them had tapped "Report spam" and typed "Fake TRAI call". Sunita's phone has a list of known spammers that it downloaded last night; this number is not on it. Her network can barely load a web page. Yet before the second ring, her screen turns red: "Likely fraud". She puts the phone face down and goes back to the dal.
At the same moment, in Pune, her son Rohit's iPhone rings with the same number. His phone asks the same question, but through a relay and in encrypted form, so that the company answering it never learns who was asked about which number.
- 500 million+ users across iOS and Android (Truecaller interim report, May 2026); more than 350 million of them in India and 50 million+ added in 2025, per Truecaller's April 2026 announcement as reported by Digital Terminal
- 476.2 million average monthly and 411.1 million average daily active users outside iOS, Q3 2026 (Truecaller, preliminary results, 19 October 2026)
- 68 billion+ spam calls identified and blocked in 2025, up from 37.8 billion in 2021 (Truecaller corporate site and blog)
- 8 shards × 21 buckets: the worked example of how Truecaller lays out its encrypted iPhone lookup database; two queries per call (block and identity); sub-100 ms with shards in memory (Truecaller Engineering, June 2025)
- 600,000+ requests a second at peak at Truecaller's ad server, for a user base of "over 400 million" (Truecaller Engineering, February 2025)
For context:
Truecaller AB was founded in Stockholm in 2009 and has been listed on Nasdaq Stockholm since 8 October 2021; India is its biggest market
Truecaller has published how its iPhone lookups work and how it rate-limits fan-out, and a Redpanda case study describes parts of its data pipeline; it has not published its Android lookup path or its spam model, so those parts of this design are labelled as ours
From 1 November 2026, the Department of Telecommunications has directed Indian operators to show the caller's registered name on 4G and higher-generation calls by default (CNAP; Inc42, October 2026); this quest designs the app-side system and compares the two in "Why Truecaller Matters"
This is the system we'll design today.
Truecaller has 500 million+ users across iOS and Android and 411.1 million daily users outside iOS, and identified 68 billion+ spam calls in 2025. On stated assumptions that is ~1 billion lookups a day, ~35,000 a second at peak against a 100,000 target, over ~6 billion numbers in ~0.9 TB of serving records with a ~45 GB hot set. The phone answers first: a saved contact, then the region's spam list of ~500,000 numbers in ~4.5 MB, signed and updated by hourly deltas. Strangers cost one request with an 800 ms deadline: the lookup service reads one precomputed record, hashed by E.164 number, from Redis or a local Cassandra replica, and hot numbers are kept as 8 copies that new verdicts overwrite, never delete. The write path scores reports and call outcomes, which outnumber reports ~500 to 1, in a stream plus a daily batch: a report counts only with proof of the call, reporters are weighted by trust and bands use hysteresis. On iPhone, Apple's relay hides who asked, single-use tokens prove a user and PIR hides the number. Every name keeps its source and region, and an unlisted number goes within 24 hours unless the community marked it as spam. The deep dives end with a scam number's morning, a spoofed bank number and CNAP in a festival scam wave.
The Interview Begins#
You're interviewing at a company that builds a communication-safety app. The interviewer holds up two phones side by side. On the left, an incoming call: a red banner, "Likely fraud · Telecom impersonation · 1,814 reports". On the right, the same screen a minute later on a different phone: a green banner, "Ramesh Kumar · Someone you may know".
"Both of these are strangers to the person holding the phone. Design the system that puts a name and a verdict on that screen before they pick up."
Users register their own number. When a call or SMS arrives from a number not in their contacts, the app shows who it probably is and whether it is spam, before the second ring. Users can search a number, report spam, suggest a name, block numbers and unlist their own; the system learns from every report and call outcome.
Requirements
- Identify an incoming number (name, category, spam verdict) within the first ring
- Block known spammers automatically, including on a weak or absent network
- Take spam reports and name suggestions from the community, and resist abuse of them
- Offer a private lookup path where the platform (iOS) requires the server not to learn the query
- Let anyone unlist their number, and handle contact data and consent honestly
"Leave out the dialler, SMS inbox, call recording, the payments and the ads business. And don't design the telecom network: you only see what the phone sees."
Phase 1: Requirements Clarification (5 minutes)#
"The phone ringing is a deadline nobody can extend, so I'll start with the clock, then the scale, then where the data comes from, because that decides what we're allowed to build."
Your Questions#
"How big is it?"
"Truecaller's May 2026 interim report says more than 500 million people use it across iOS and Android; Digital Terminal, reporting its April announcement, put more than 350 million of them in India. It reports users outside iOS every quarter: its preliminary Q3 2026 figures, published on 19 October ahead of the full report, are 476.2 million monthly and 411.1 million daily on average."
"And how much spam?"
"Truecaller says it identified and blocked more than 68 billion spam calls in 2025; it reported 37.8 billion for 2021 and 46.9 billion identified in 2023. According to ETV Bharat in May 2026, Truecaller's latest spam report ranked India the fifth most spammed country."
"How fast must the answer be?"
"On iPhone, Truecaller's engineers wrote that the block decision 'must be made quickly, before the phone even rings', and that with data in memory 'sub-100ms latency becomes a realistic goal'. On Android the app can look a number up as the call arrives. Assume the verdict must be on screen within the first ring."
"Where do names come from?"
"Truecaller's own explanation, from 2022: from people's profiles, 'public partnerships', and what the community suggests; if most users save or report a number as 'Insurance spam', that's what caller ID shows. Its privacy policy also says it processes signals about the numbers of people who don't use the app, such as how often a number calls its users. That's where the privacy questions start."
"Does it have to work offline?"
"Truecaller refreshes a regional list of top spammers daily and keeps it on the phone. In early 2026 it launched Truecaller Lite for low-end phones with less reliable connectivity. Design for the one-bar case."
"Can I search by name?"
"Treat it as number-to-name only. Truecaller's privacy centre says profiles are private to name searches by default and a phone number isn't shown for a name unless that person approves a contact request."
Functional Requirements#
- Identify: for any incoming call or SMS from a number not in the user's contacts, return name, category (person, business, spam type), spam verdict and evidence ("1,814 reports")
- Block: auto-block or silence numbers above a spam threshold, per the user's setting; block series and country codes
- Report and suggest: report a number as spam with a category and a free-text label; suggest a name; mark "not spam"
- Offline: a regional top-spammer list on the device, refreshed daily, used when the network is slow or absent
- Private lookup: a mode in which the server answers without learning who asked or which number (required on iOS)
- Control: register and edit your own name; unlist your number; access, correct and delete your data
Non-Functional Requirements#
Latency (design targets)
- Verdict on screen within ~1 s of the call arriving, end to end, p99, on a normal mobile network
- Server lookup p99 < 30 ms inside the region; private (PIR) lookup < 100 ms server time
- With no usable network: a local verdict in < 20 ms from the on-device list
Scale
- ~410 million daily users; ~1 billion lookups a day, peaking around 35,000 a second (estimated below)
- Billions of numbers in the directory; a few hundred million of them ever call strangers
Correctness and trust
- A verified business or a user's own registered name is never overridden by crowd reports
- A wrong spam label must be reversible within hours, not days
- An unlisted number disappears from search within 24 hours (Truecaller's published promise)
Phase 2: Back of the Envelope Estimation (5 minutes)#
"Two published anchors: about 411 million daily users (a preliminary Q3 figure) and about 68 billion spam calls a year. Everything else is my assumption, and I'll check one against the other."
Before the numbers: how many lookups is 68 billion spam calls?
Truecaller identified about 68 billion spam calls in 2025. How many is that a day and a second? If spam is roughly 40% of calls from numbers not in the receiver's contacts, how many unknown-number calls is that a day? How does that compare with 411 million daily users each getting about one unknown call a day?
One strong answer
- Spam: 68 billion ÷ 365 ≈ 186 million a day ≈ 2,150 a second on average.
- Unknown calls: 186 million ÷ 0.4 ≈ 465 million a day.
- Per user: 411 million daily users × ~1.2 unknown calls ≈ 490 million a day: the two estimates agree.
- So the incoming-call lookup is about half a billion requests a day before adding searches, SMS senders and call-log refreshes. Assume those double it: ~1 billion lookups a day.
Lookup Traffic#
| Value | |
|---|---|
| Daily active users outside iOS (published, preliminary, Q3 2026) | ~411 million |
| Unknown-number calls per user a day (assumed) | ~1.2 → ~490 million |
| Searches, SMS senders, call-log enrichment (assumed equal) | ~490 million |
| Total lookups a day | ~1 billion |
| Average | 1 billion ÷ 86,400 ≈ ~11,600 a second |
| Evening peak (assumed 3× average) | ~35,000 a second |
| Design target | ~100,000 a second |
Conclusion: modest for key-value reads; the hard parts are the deadline (one ring, bad networks) and the skew (a scam number looked up by thousands of phones in one hour).
The Directory#
- Numbers known: Rest of World reported in 2022 that Truecaller's 2021 prospectus counted 5.7 billion unique phone identities; assume ~6 billion today
- Serving record per number: best name, category, spam score, report count, verified flag, last activity: ~150 bytes → 6 billion × 150 B ≈ ~0.9 TB
- Name candidates: ~3 per number at ~40 bytes, kept apart from the serving record: ~0.7 TB
- Replicas: 3 per region → ~5 TB per region
- The hot set: numbers that call strangers this week, assumed ~300 million, × 150 B ≈ ~45 GB: fits in a memory cache cluster
Conclusion: the whole directory is small enough to replicate to each region; the part that matters on any given day fits in RAM.
Reports and Signals#
- Explicit reports: if 1 spam call in 100 is reported, 68 billion × 1% ≈ 680 million reports a year ≈ 1.9 million a day ≈ ~22 a second, peaking at a few hundred
- Implicit signals: every identified call produces an outcome (answered, declined, blocked, call length): ~1 billion events a day ≈ ~12,000 a second
- Event size: ~200 bytes → ~200 GB a day of signals
Conclusion: call outcomes outnumber reports about 500 to 1. Reports name the problem; outcomes measure it. Both feed a streaming pipeline, never the lookup database directly.
The Phone#
- Regional top spammers: assume 500,000 numbers for a large Indian region
- Stored as 8-byte number hashes plus a 1-byte category, sorted: 500,000 × 9 B ≈ 4.5 MB
- Daily churn (spammers change numbers): assume 5% → 25,000 adds and removes ≈ ~225 KB a day, compressed further
- On a 2G-class link (~50 kbit/s): ~225 KB takes ~40 seconds: fine overnight on Wi-Fi or in the background, never during a call
Conclusion: the phone can carry the region's worst numbers, and a day's changes cost less than a photo. It can't carry the number that started calling this morning.
Key Metrics Summary#
| Metric | Value |
|---|---|
| Users, iOS and Android (Truecaller, May 2026) | 500 million+ (350 million+ in India, per Digital Terminal) |
| Daily users outside iOS (Truecaller, Q3 2026, preliminary) | 411.1 million |
| Spam calls identified and blocked (Truecaller, 2025) | 68 billion+ (~2,150 a second) |
| Lookups a day / peak / design target | ~1 billion / ~35,000 a second / ~100,000 a second |
| Numbers in the directory | ~6 billion, assumed (5.7 billion in 2021, via Rest of World) |
| Hot set in memory | ~300 million numbers, ~45 GB |
| On-device list / daily delta | ~4.5 MB / ~225 KB |
| Private lookup on iPhone (Truecaller goal) | under 100 ms, two queries a call |
Requirements and estimation
Q1/2 Truecaller identified about 68 billion spam calls in 2025. Roughly how many is that a second, on average?
Answer & explanation
Answer: B
68 billion ÷ 365 ÷ 86,400 ≈ 2,150 a second.
Q2/2 Why is ~35,000 lookups a second at peak not the hard part of this design?
Answer & explanation
Answer: B
A single key read is cheap. A verdict that must arrive before the second ring, on one bar of signal, for a number thousands of phones are asking about at once, is not.
Phase 3: High-Level Design (10 minutes)#
Sketch it first: Sunita's phone at 10:41
Sunita's phone rings with a number it doesn't know. What does the app check first, before using the network at all? If it does go to the network, what's the smallest request it can send, and how long can it wait? Where does the answer come from on the server, and how did a number that started calling at 9:12 already have a verdict by 10:41? And what's different when the same number rings Rohit's iPhone?
One strong answer
- The phone first: contacts, then the on-device spam list, in milliseconds and with no network. Neither knows this morning's number.
- Then one small request with a deadline (about 800 ms) to the regional edge. If it's late, show "Unknown number" and repaint if the answer lands during the ring.
- On the server: a cache in front of a replicated store of precomputed serving records: name, category, spam score, evidence.
- Why it already knew: the spam pipeline streams reports and call outcomes; when the score crossed a threshold it rewrote the record within minutes, and an hourly delta carried it to phones in the region.
- Rohit's iPhone: two encrypted queries through a relay; the server answers without learning the number or the asker.
"Two paths that meet only in the number store. A read path that must answer within one ring: phone first, then a regional cache, then a replicated store of precomputed records. And a write path that turns reports and call outcomes into those records: a stream for the fast signals, a daily batch for the careful ones. The read path never computes a score; it only fetches one."
System Architecture#
%% title: Caller ID: a deadline-bound read path, fed by a streaming spam pipeline
flowchart TD
subgraph L1["Layer 1 · Phones"]
direction LR
AND@{ icon: "tech:android", label: "Android app<br/>contacts, spam list, live lookup" }
IOS@{ icon: "tech:apple", label: "iPhone<br/>Live Caller ID, encrypted" }
end
subgraph L2["Layer 2 · Edge"]
direction LR
GW@{ icon: "sd:gateway", label: "Regional API edge<br/>auth, rate limits" }
RELAY@{ icon: "sd:tunnel", label: "Oblivious HTTP relay<br/>run by Apple" }
end
subgraph L3["Layer 3 · Read path"]
direction LR
LOOK["Lookup service<br/>normalise, resolve, deadline"]:::hot
PIR["PIR service<br/>encrypted shards in memory"]:::core
CACHE@{ icon: "tech:redis", label: "Redis<br/>hot serving records" }
end
subgraph L4["Layer 4 · Write path"]
direction LR
INGEST["Report and signal intake<br/>validate, rate limit"]:::service
BUS@{ icon: "tech:kafka", label: "Kafka-API log<br/>reports, call outcomes" }
SCORE["Spam and name scoring<br/>stream plus daily batch"]:::core
end
subgraph L5["Layer 5 · State"]
direction LR
STORE@{ icon: "tech:cassandra", label: "Number store · Cassandra<br/>serving records, per region" }
LISTS@{ icon: "sd:object-storage", label: "Spam list builds<br/>daily list, hourly deltas" }
end
AND ==> GW ==> LOOK
IOS ==> RELAY ==> PIR
LOOK ==> CACHE
CACHE -.->|"miss"| STORE
AND -->|"reports, outcomes"| GW
GW --> INGEST ==> BUS ==> SCORE
SCORE ==>|"rewrite record"| STORE
SCORE --> LISTS
LISTS -.->|"download"| AND
STORE -.->|"rebuild shards"| PIR
class AND,IOS client
class GW,RELAY edge
class CACHE cache
class BUS queue
class STORE,LISTS storeKEY INSIGHTS:
- The phone is the first tier. Contacts and the on-device spam list answer many calls with no network at all; the server is asked only about strangers the phone doesn't already know
- Precompute the answer, fetch it in one read. Scores and best names are computed on the write path and stored as one small serving record per number, so a lookup is a key read with a deadline, never a query
- Two read paths, one truth. Android asks the lookup service in the clear over TLS; iPhone asks a PIR service through a relay. Both serve the same records, rebuilt from the same store
Data Flow: Sunita's Call at 10:41#
%% title: Sunita's call at 10:41, on one bar of signal sequenceDiagram autonumber participant P as Sunita's phone participant L as On-device list participant E as Regional edge participant S as Lookup service participant C as Hot cache P->>P: Not in contacts P->>L: In last night's spam list? L-->>P: No P->>E: Look up number, deadline 800 ms E->>S: Token valid, under rate limit S->>C: Serving record for this number C-->>S: Likely fraud, 1,814 reports, rewritten 10:22 S-->>P: Verdict, about 140 bytes P->>P: Red screen before the second ring
MEANWHILE, IN PARALLEL:
- Since 9:12, every decline, block and report of this number has flowed through the intake into the log; at 10:22 its score crossed the fraud threshold and the scorer rewrote its serving record in every region
- At 10:30 the region's hourly delta included it; Sunita's phone hadn't fetched it yet, which is why the live lookup mattered
- Her unanswered call becomes one more signal: another vote
High-level design
Q1/2 Why does the lookup service fetch a precomputed record instead of computing a spam score per request?
Answer & explanation
Answer: B
Read path: one key read with a deadline. Write path: everything expensive.
Q2/2 What does Truecaller learn when Rohit's iPhone asks about the same number?
Answer & explanation
Answer: C
Relay hides the who; Privacy Pass proves "a user" without saying which; PIR hides the number.
Phase 4: Deep Dives (20 minutes)#
Deep Dive 1: The Lookup Path in Milliseconds (Week 1 Concepts: Partitioning, Replication; Week 4: Multi-Tier Caching)#
"The phone ringing sets the deadline. Everything about the read path, the key, the record, where it's stored, how many hops, exists to make one answer arrive inside it, from anywhere in India, on any network."
This builds on Week 1, Day 1 · Partitioning Deep Dive, Week 1, Day 2 · Replication Trade-offs and Week 4, Day 5 · Multi-Tier Caching.
One ring, one bar
The same number reaches three phones as "+91 98xxx xxx01", "098xxx xxx01" and "98xxx xxx01". The directory holds about 6 billion numbers. You have a region in Asia serving India and one in Europe. Sunita's network has a round trip of about 600 ms. How do you key and partition the numbers? Why not partition by prefix? Where do replicas live, and how stale may they be? And what do you show if the answer hasn't arrived when the phone starts ringing?
One strong answer
- Normalise on the phone to E.164 (+919800000001) using the SIM's country; the key is a hash of that string. Three spellings, one key.
- Hash, not prefix. Number series are allocated in blocks, and a new block fills with new SIMs, the kind bought in bulk; prefix ranges would put that block on one partition.
- Replicate to each region, read locally. A verdict a minute old is fine; a cross-region read on the call path is not.
- One request, about 140 bytes, over a connection the app keeps warm: a 600 ms round trip leaves time for one exchange, not a handshake plus a query.
- Show what you know, then improve the screen if the answer lands during the ring.
What is published: Truecaller hasn't published its Android lookup path. It has said that EU users' data is processed in data centres in Sweden and that Indian user data is stored in India (Truecaller, October 2022); its Kafka pipeline ran in two regions with the most critical one in Asia (Redpanda case study); and its job listings name Google Cloud, Kubernetes, Cassandra, ScyllaDB, Redis and Aerospike. For iPhone, its engineers wrote that keeping "all shard data in memory" avoids expensive disk reads and makes the lookup CPU-bound (Truecaller Engineering, June 2025). It launched Truecaller Lite in early 2026 for low-end devices "with less reliable connectivity" (Truecaller interim report, May 2026).
%% title: Five tiers, each answering what it can within the ring
flowchart TD
CALL@{ icon: "sd:phone", label: "Incoming call<br/>number normalised to E.164" }
CON["Contacts on the phone<br/>a saved name wins"]:::service
DEV["On-device spam list<br/>region, refreshed daily"]:::service
EDGE@{ icon: "sd:cache", label: "Lookup pod memory<br/>seconds-long TTL" }
HOT@{ icon: "tech:redis", label: "Redis<br/>hot records, ~45 GB" }
DB@{ icon: "tech:cassandra", label: "Cassandra<br/>all records, local replica" }
CALL ==> CON
CON -->|"unknown"| DEV
DEV -->|"not listed"| EDGE
EDGE -->|"miss"| HOT
HOT -.->|"miss"| DB
class CALL client
class EDGE,HOT cache
class DB store- The serving record is the product. Per number: display name, category, spam band, report count, verified flag, version, timestamp. Name candidates, report text and history live in tables the call path never touches.
- Hash partitions, local replicas. A few thousand virtual nodes per region; each record is one row, read from one local replica. The scorer writes each new version to every region allowed to serve it (spam verdicts everywhere; names only inside their jurisdiction, Deep Dive 6), and a newer version always wins, so out-of-order replication can't resurrect an old verdict.
- Most lookups never reach the store. Pod memory absorbs repeats within seconds, Redis holds the hot set, Cassandra serves the long tail; "no record" is cached too, for a few minutes.
- Budget the ring. Contacts and the device list: under 20 ms. Network: about 800 ms, of which the server gets under 30 ms. The rest belongs to a sleepy radio on one bar, which is why there is only one round trip.
# lookup/resolve.py
"""
Resolving an incoming number on the phone (illustrative). Each tier answers what
it can; the network is asked once, with a deadline, and a late answer may still
update the ringing screen.
"""
import hashlib
DEADLINE_MS = 800
def key(e164: str) -> bytes:
return hashlib.sha256(e164.encode()).digest()[:8] # the same 8-byte hash the device list uses
def resolve(number: str, sim_country: str, phone, net) -> dict:
e164 = phone.normalise(number, sim_country) # "098..." and "+91 98..." become one key
if (name := phone.contacts.get(e164)) is not None:
return {"name": name, "source": "contacts"} # a saved name always wins
local = phone.spam_list.get(key(e164)) # last daily list plus hourly deltas
if local is not None and local.blocking:
return {"verdict": local.category, "source": "device"}
fut = net.lookup_async(e164, token=phone.token(), timeout_ms=DEADLINE_MS)
phone.screen.on_late_answer(fut) # repaint if it lands while ringing
answer = fut.result_or_none(DEADLINE_MS)
if answer is not None:
return answer # name, category, score band, reports
if local is not None:
return {"verdict": local.category, "source": "device"}
return {"name": None, "source": "unknown"} # still better than guessingDeep Dive 2: Hot Numbers: When Every Phone Asks About the Same Scam (Week 1 Concepts: Hot Keys and Skew; Week 4: Thundering Herd)#
"Caller ID traffic is skewed by design. A scammer's whole business is calling thousands of strangers an hour, so the numbers we're asked about most are exactly the ones whose verdict changes fastest. And the most-asked numbers of all are legitimate: a bank, a courier, a hospital."
This builds on Week 1, Day 4 · Hot Keys and Skew, Week 4, Day 1 · Caching Patterns and Week 4, Day 3 · Thundering Herd.
50 calls a second from one number
A robocaller dials from one number 50 times a second across UP and Bihar; every call is a lookup of the same key. A courier company's dispatch number is looked up 2,000 times a second all evening. At 10:22 the robocaller's record is rewritten from "Suspected spam" to "Likely fraud". What happens to the one Redis node holding each key? What happens if the rewrite deletes the cached record and 50 lookups a second miss at once? And how do you know which keys are hot before the node falls over?
One strong answer
- Spread the hot key. Keep 8 copies of a hot record under suffixed keys on different nodes; readers pick one at random. The courier's 2,000 a second becomes 250 per node.
- Absorb repeats in the pod with a few-second in-memory cache, and coalesce simultaneous misses into one store read (single-flight).
- Overwrite, never delete. The scorer writes version 43 over 42 in every copy, so there's never a miss to stampede.
- Detect heat on the write path. The pipeline already counts calls per number per minute (a spam signal); above a threshold it writes the record with copies before the reads arrive.
What is published: Truecaller says it updates its spam lists daily "because spammers change their numbers frequently" (Truecaller CNAP comparison page), that its lists are specific to the user's region, and that it shows spam statistics per number: where it calls from, the time of day it's most active, how often it has been reported and "the number of spam calls made over 60 days" (Truecaller spam-blocking page). Its privacy policy lists "the velocity and frequency of communication of a given number with our Users" among the signals it processes (Truecaller privacy policy, July 2026). How its caches handle hot numbers isn't published.
- Heat and spam are the same measurement: calls per number per minute feeds both the scorer and the cache planner.
- Two kinds of hot numbers. A scam number is hot for hours and changes often; a bank's number is hot for years and rarely changes, so give it a long TTL and ship it to phones in a verified-business list.
- Write-through with versions. A stale write (42 after 43) is ignored everywhere; pod caches expire in seconds.
- Watch the skew, not the average: the hottest Redis node's load against the median; above about 3× a hot key escaped detection.
# lookup/hot.py
"""
Hot numbers (illustrative). Records the pipeline flags as hot are written as
several copies on different nodes; readers pick one at random. Updates overwrite
every copy with a newer version, so a verdict change never creates a miss.
"""
import random
COPIES = 8
def read(redis, store, flight, k: str, hot: bool):
rk = f"{k}#{random.randrange(COPIES)}" if hot else k
rec = redis.get(rk)
if rec is not None:
return rec
return flight.do(k, lambda: store.get(k)) # single-flight: one store read per pod per miss
def write(redis, store, k: str, rec, hot: bool):
store.put_if_newer(k, rec, version=rec.version)
for rk in ([f"{k}#{i}" for i in range(COPIES)] if hot else [k]):
redis.set_if_newer(rk, rec, version=rec.version) # overwrite, never delete: no miss to stampedeLookup path and hot numbers
Q1/3 Why partition the directory by a hash of the number rather than by prefix?
Answer & explanation
Answer: B
Hashing spreads related numbers; prefix ranges concentrate exactly the numbers that get hot together.
Q2/3 A scam number's verdict changes at 10:22 while it's being looked up 50 times a second. What stops a stampede on the store?
Answer & explanation
Answer: B
Delete-then-miss creates the herd; versioned overwrite avoids it.
Q3/3 How does the system learn a number is hot before its cache node is overloaded?
Answer & explanation
Answer: B
One counter, two uses: early spam signal and hot-key detector.
Deep Dive 3: Names and Spam Scores From the Crowd (Week 8 Concepts: Event Ingestion, Streaming vs Batch; Week 1: Rate Limiting)#
"The directory is written by its readers. That's its strength, because 400 million people notice a new scam within minutes, and its weakness, because anyone who wants a rival's number marked as spam, or their own scam number cleared, can join the crowd too. So the scorer never counts reports; it weighs evidence."
This builds on Week 8, Day 1 · Event Ingestion, Week 8, Day 2 · Streaming vs Batch Processing and Week 1, Day 3 · Rate Limiting at Scale.
Three numbers, three crowds
Number A started calling at 9:12; by 10:20 it has called 9,000 distinct people, 7,200 declined within two rings, and 310 reported it. Number B, a tailor's shop in Lucknow, gets 40 spam reports in an hour from accounts created yesterday, none of which has a call from B in its log. Number C, a delivery rider's phone, is declined by most people it calls because they don't recognise it, and 12 people report it. Which of these should turn red, how fast, and on what evidence? How do you stop B's attackers? How does C's owner get the label removed?
One strong answer
- A turns red within the hour, from behaviour as much as reports: thousands of distinct callees, almost none with it saved, short calls, a brand-new number, plus hundreds of reports from established accounts that received its calls.
- B stays clean. A report counts only if the reporter's call log shows a call from that number, and each reporter's weight depends on account age, verification and past agreement with final verdicts. Forty new accounts with no calls weigh nearly nothing, and the burst is itself a brigading signal.
- C is the hard case. Declines mean "unknown", not "spam", and C's answered calls are long. Cap C at "Suspected spam" until trusted reporters agree, and let the owner verify the number and dispute; a verified owner overrides the crowd.
- Speed: fast signals are scored in a stream within minutes; the weights and model are retrained daily in batch.
What is published: Truecaller's 2022 explainer says names come from user profiles, "public partnerships" and community suggestions, and that "if an insurance spam number is saved or reported as 'Insurance spam' by the maximum number of our users, then that is what the caller ID will show". Its CNAP comparison page says a number is marked "Likely important" when people generally answer it. Its privacy policy lists "number longevity" and "trust/reputation signals", and, for numbers of people who don't use the app, "the velocity and frequency of communication of a given number with our Users" (July 2026). In 2024 Kunal Dua, its vice president of search, told TechCrunch that Truecaller had tested "multiple dozen algorithms" to identify spam, each also learning from user feedback, and that some legitimate businesses may be blocked temporarily "before our community, and before our AI algorithms can identify that this is a legitimate business". A Redpanda case study says spam analytics rely on the real-time pipeline, which moved from self-managed Kafka to Redpanda, cutting infrastructure cost by 58% and latency by almost half.
%% title: From a report or a declined call to a new verdict in minutes
flowchart TD
APP@{ icon: "sd:phone", label: "Phone<br/>report, decline, block, answer" }
IN["Intake<br/>call-log proof, per-user token bucket"]:::service
LOG@{ icon: "tech:kafka", label: "Kafka-API log<br/>partitioned by number" }
WIN["Stream windows<br/>1 h and 24 h per number"]:::core
SCORE["Scorer<br/>weighted evidence, bands"]:::hot
BQ@{ icon: "tech:bigquery", label: "BigQuery<br/>daily features, retraining" }
OUT@{ icon: "sd:database", label: "Serving records<br/>new version per change" }
APP ==> IN ==> LOG ==> WIN ==> SCORE
LOG --> BQ
BQ -.->|"model and reporter trust, daily"| SCORE
SCORE ==> OUT
class APP client
class LOG queue
class BQ service
class OUT store- Partition the log by the reported number, so one stream task keeps each number's windows: distinct callees, declines, reports and call lengths over the last hour and day.
- Events carry proof. A report includes the call event it refers to; the intake checks the device's outcome stream has that call. Reports about calls that never happened never reach the log.
- Weights from the batch, scores from the stream. A daily job over the full history recomputes each reporter's trust (agreement with final verdicts, account age, verification) and retrains the model; the stream applies them to the last hour. Distinct reporters count, not reports, and no reporter's influence on one number is unbounded.
- Bands with hysteresis, so a verdict doesn't flicker between calls; "Likely fraud" needs fraud-category reports from trusted reporters.
- Names follow the same pattern. The owner's own profile (final once verified) beats verified business records, which beat community suggestions weighted by trust and recency. When a recycled number gets a new owner, old candidates stop counting. Overrides by verified owners or businesses are explicit, versioned and logged.
# spam/score.py
"""
Scoring a number from one hour of evidence (illustrative). Reports count once per
distinct reporter, weighted by that reporter's trust from the daily batch; call
outcomes carry as much weight as reports; verified owners set a floor.
"""
import math
BANDS = [(0.85, "LIKELY_FRAUD"), (0.65, "LIKELY_SPAM"), (0.40, "SUSPECTED_SPAM")]
STEP_DOWN = 0.10 # hysteresis: must fall this far below a band to leave it
def score(win, trust: dict, model) -> float:
reporters = {r.user: r for r in win.reports if r.has_call_proof}
report_weight = sum(min(trust.get(u, 0.05), 1.0) for u in reporters)
features = {
"reports": math.log1p(report_weight),
"fraud_share": sum(r.category == "fraud" for r in reporters.values()) / max(len(reporters), 1),
"distinct_callees": math.log1p(win.distinct_callees),
"decline_rate": win.declined / max(win.calls, 1),
"short_call_rate": win.short_calls / max(win.answered, 1),
"saved_by_callees": win.callees_with_number_saved / max(win.distinct_callees, 1),
"number_age_days": win.number_age_days,
}
return model.predict(features) # retrained daily on verdicts users confirmed or undid
def band(prev: str, s: float, verified_owner: bool) -> str:
if verified_owner:
return "NOT_SPAM" # a dispute upheld after verification overrides the crowd
for threshold, name in BANDS:
if s >= threshold:
return name
if prev == name and s >= threshold - STEP_DOWN:
return name # stay put: no flicker between calls
return "NONE"Rate limits that hold across many pods#
"Two things need limits on many autoscaling pods: writes (so nobody floods a number with reports) and reads (so nobody scrapes the directory one number at a time)."
What is published: Truecaller's ad server has the mirror-image problem: many pods calling partners that each allow a fixed rate, at peaks of over 600,000 requests a second. A shared limiter in Redis would add "additional network calls and latency", so each pod runs a token bucket (Resilience4j's RateLimiter) set to the partner's limit divided by the number of pods, recalculated when the Kubernetes autoscaler changes the pod count, which pods learn through the Kubernetes Watch API (Truecaller Engineering, February 2025).
- Global caps, split locally: "at most 2,000 report events a second about any one number" becomes 2,000 ÷ N per pod, recomputed when N changes; no shared counter on the hot path.
- Per-user caps need per-user state: a reporter's daily cap and a searcher's hourly cap live in a small store sharded by user, so there's no hot key.
- Scraping is read-path abuse. Cap manual searches per account and device, require a verified number to register, and score accounts whose searches walk number ranges in order. Caller ID for real incoming calls is unaffected.
# limits/per_pod.py
"""
A global rate split across pods (illustrative), as Truecaller described for its
ad server: each pod gets limit / N tokens a second, and N is updated from
Kubernetes watch events when the autoscaler adds or removes pods.
"""
import time
class PodBucket:
def __init__(self, global_qps: float, pods: int):
self.global_qps = global_qps
self.set_pods(pods)
self.tokens, self.t = self.rate, time.monotonic()
def set_pods(self, pods: int): # called from the watch on scale events
self.rate = self.global_qps / max(pods, 1) # 150,000 / 3 = 50,000; / 4 = 37,500
def try_acquire(self) -> bool:
now = time.monotonic()
self.tokens = min(self.rate, self.tokens + (now - self.t) * self.rate)
self.t = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False # timeout 0: reject now, don't queueDeep Dive 4: Offline and on One Bar: The Phone as a Cache (Week 4 Concepts: Multi-Tier Caching, Cache Invalidation)#
"In the places where scam calls do the most damage, the network is weakest. So the most important cache in this system isn't in a data centre; it's a few megabytes on a phone that may not have spoken to us in three days."
This builds on Week 4, Day 5 · Multi-Tier Caching and Week 4, Day 2 · Cache Invalidation.
Sunita's phone at night
Sunita's region has about 500,000 numbers worth blocking. Her phone is a low-end Android with little free storage and patchy data; some days it doesn't connect at all. A number is added to the list at 10:22, and another, a rider's number wrongly flagged yesterday, is cleared at 11:00. How do you get both changes to her phone? What if she has missed three days of updates? How does the phone check a number in microseconds while it rings? And what can the list never do?
One strong answer
- A versioned base plus deltas. The phone keeps base 900 and applies deltas 901, 902 … (adds and removes): a nightly base, plus hourly deltas with the morning's new scams and the clearings. Removals are as urgent as additions.
- Three days missed? If the phone is older than the oldest delta kept (7 days), it fetches a fresh base; otherwise the deltas in order. Files are signed and versioned, so a broken download is retried, never half-applied.
- Lookup: a sorted, memory-mapped array of 8-byte hashes; binary search takes about 19 comparisons for 500,000 entries.
- What it can't do: know a number that started calling after the last delta, or show a name. The list is the floor; the live lookup is the ceiling.
What is published: Truecaller says it "refreshes its spam lists daily to block calls from the most notorious spammers in your area", that Premium users get the list updated automatically while free users update it by hand, and that a phone without an updated list still shows a red caller ID for a spam call (Truecaller spam-blocking page). Its iPhone history shows the limits of offline lists: Apple's Call Directory Extension (2016) preloaded numbers but had little memory, could "crash or silently fail", had "a hard cap on the number of entries", and was refreshed in the background so rarely for infrequent users that data "could quickly become stale"; Truecaller split its numbers across several extensions to raise the cap (Truecaller Engineering, June 2025). In early 2026 it launched Truecaller Lite for low-end devices with less reliable connectivity (Truecaller, May 2026).
%% title: The spam list, from the nightly build to a phone that was offline for three days
flowchart TD
SC["Scorer<br/>verdict changes all day"]:::core
BUILD@{ icon: "sd:cron", label: "List builder<br/>nightly base, hourly deltas" }
CDN@{ icon: "sd:cdn", label: "CDN<br/>signed files per region and version" }
PUSH@{ icon: "tech:firebase", label: "FCM tickle<br/>urgent delta ready" }
PH@{ icon: "sd:phone", label: "Phone<br/>base 900 plus deltas" }
OLD{"More than 7 days behind?"}
SC ==> BUILD ==> CDN
BUILD -.-> PUSH -.-> PH
PH ==> OLD
OLD -->|"no: deltas in order"| CDN
OLD -->|"yes: fresh base"| CDN
class BUILD service
class CDN edge
class PUSH external
class PH client- Regional, not global. A list per region (a telecom circle, say) keeps it small: today's UP East scam is in the UP East delta, not Kerala's.
- Hashes for size, not secrecy. Indian mobile numbers are a small space, so a hash can be reversed by brute force; 8-byte hashes are just compact. The list holds only spam numbers, never names.
- Fetch on the user's terms. Bases on Wi-Fi or while charging; deltas of a few kilobytes on mobile data; a silent push tells a region an urgent delta exists, and phones fetch it with jitter.
- The device list blocks; the server explains. A list hit can block at once; a live answer, if it arrives, adds name, category and evidence. Settings show "spam list updated 3 days ago": staleness is visible, not silent.
# device/spamlist.py
"""
The on-device spam list (illustrative): sorted 8-byte hashes with a category,
a version, and signed deltas applied in order; a phone too far behind fetches a
fresh base. Lookups are a binary search.
"""
import bisect
MAX_DELTA_GAP = 7 * 24 # hourly deltas are kept for 7 days
def get(lst, h: bytes):
i = bisect.bisect_left(lst.keys, h)
return lst.cats[i] if i < len(lst.keys) and lst.keys[i] == h else None
def sync(phone, cdn, region: str):
if cdn.latest_version(region) - phone.spam_list.version > MAX_DELTA_GAP:
phone.spam_list = cdn.base(region) # too far behind: start again
for d in cdn.deltas(region, after=phone.spam_list.version):
if d.from_version != phone.spam_list.version or not d.signature_ok():
break # retry later, never half-apply
phone.spam_list.remove_all(d.removes) # clearings first: a wrong red label is our harm
phone.spam_list.upsert_all(d.adds)
phone.spam_list.version = d.to_versionThe crowd and the phone
Q1/3 Forty brand-new accounts report a tailor's number as spam within an hour, but none has a call from it in its log. What happens?
Answer & explanation
Answer: B
Weigh evidence, not volume: a report must refer to a call that happened, and reporters earn weight.
Q2/3 Truecaller's ad server splits a partner's 150,000 requests a second across pods. How does each pod know its share when the autoscaler adds a fourth pod?
Answer & explanation
Answer: B
Local token buckets, sized by the pod count, updated from watch events.
Q3/3 Sunita's phone has been offline for ten days. Deltas are kept for seven. What does it do?
Answer & explanation
Answer: B
Deltas only work from a known version; past the window, start from a new base.
Deep Dive 5: Private Lookups on iPhone: Answering Without Knowing the Question (Week 9 Concepts: Security Architecture; Week 1: Rate Limiting)#
"A caller ID lookup is a sensitive record: 'this person got a call from that number at this time'. On Android we receive it over TLS and promise to handle it well. Apple's Live Caller ID Lookup asks for more: that we can answer without ever holding that record. Three things have to be hidden: who's asking, which number, and even whether they wanted a block or a name."
This builds on Week 9, Day 5 · Security Architecture and Week 1, Day 3 · Rate Limiting at Scale.
Rohit's iPhone rings
The same number rings Rohit's iPhone in Pune. iOS will ask your server about it, but your server must not learn Rohit's identity, his IP address, or the number. You still need to know the request comes from a real user (not a scraper hammering your servers for free), and the answer must arrive before the phone rings. How do you hide the IP? How do you authenticate someone without identifying them, and rate-limit them? How can a server return the record for a number it isn't told? What does that cost, and what knob trades privacy for speed?
One strong answer
- Hide the who: an Oblivious HTTP relay run by Apple sees Rohit's IP but only ciphertext; the server sees the query but only the relay's address.
- Authenticate without identity: while signed in, the app collects single-use Privacy Pass tokens; each query spends one. The server checks a token is genuine and unspent but can't link it to its owner. The batch size is the rate limit.
- Hide the number: the phone maps the number to "shard 3, bucket 11" using published layout metadata, and sends the shard index plus an encrypted selector (1 at bucket 11, 0 elsewhere). The server multiplies it with every bucket of shard 3 and returns the encrypted result, which only the phone can decrypt.
- Hide the intent: iOS always sends both a block and an identity query.
- The knob: every query touches the whole shard, so smaller shards are faster but reveal more about which number it could be; Truecaller calls it "a classic trade-off between privacy and latency".
What is published: Truecaller's engineers described the path (June 2025). After years of workarounds (copy and paste in 2009, a preloaded directory in 2016, a Siri shortcut reading the call screen with OCR in 2023), iOS 18's Live Caller ID Lookup changed things. Queries arrive through an Oblivious HTTP relay (RFC 9458), so "all incoming queries look identical"; the app issues "a set of single-use tokens to authenticated users" (Privacy Pass, RFC 9578), and the backend checks a token is genuine and unused but "cannot trace the token back to any specific user". The database is split into shards (8 in their example) and buckets (21); the client sends an encrypted bitmask and the server returns an encrypted dot product with the shard. iOS sends "two requests: one for blocking and one for identity" on every call; the block answer is one bit and must come "before the phone even rings". With shards in memory, "sub-100ms latency becomes a realistic goal". All Truecaller learns is that "a Truecaller user" queried something "possibly in shard 3".
%% title: Rohit's iPhone asks, and Truecaller never learns who asked about what sequenceDiagram autonumber participant A as Truecaller app participant I as Rohit's iPhone participant R as Apple's relay participant T as PIR service A->>T: Signed in, fetch a batch of single-use tokens Note over I: later, a call from an unknown number I->>I: Number maps to shard 3, bucket 11 I->>R: Block query, encrypted selector, token 1 I->>R: Identity query, encrypted selector, token 2 R->>T: Both queries, without Rohit's IP or device T->>T: Tokens genuine and unspent, dot product over shard 3 T-->>R: Encrypted results R-->>I: Relayed back I->>I: Decrypt, find the number in bucket 11, block or show the name
- Two databases, two cadences (our design): a small block database (one bit per number) rebuilt with the hourly deltas, and a larger identity database rebuilt less often, both from the Android path's serving records, loaded beside the old version and switched atomically.
- Shard size is a budget: big enough that "shard 3" says almost nothing, small enough that one core answers well inside the ring.
- Rate limiting without identity: cap tokens per account per day at issue; keep a set of spent token IDs per key epoch and reject repeats.
- Capacity is CPU, doubled: two homomorphic queries per call over a full shard. Plan cores, with headroom; a slow block bit is a call that rings when it shouldn't.
# pir/toy.py
"""
The idea behind the private lookup (illustrative, in the clear). The real system
encrypts the selector with homomorphic encryption (Apple's swift-homomorphic-
encryption), so the server computes the same dot product without seeing which
bucket is selected.
"""
import hashlib
SHARDS, BUCKETS = 8, 21 # the worked example in Truecaller's post
def place(e164: str) -> tuple:
h = int.from_bytes(hashlib.sha256(e164.encode()).digest()[:8], "big")
return h % SHARDS, (h // SHARDS) % BUCKETS # deterministic: client and server agree
def client_query(e164: str):
shard, bucket = place(e164)
selector = [1 if b == bucket else 0 for b in range(BUCKETS)]
return shard, encrypt(selector) # the shard index is visible, the bucket is not
def server_answer(shards, shard: int, enc_selector):
rows = shards[shard] # every bucket of the shard, held in memory
return sum_encrypted(enc_selector[b] * rows[b] for b in range(BUCKETS)) # touches the whole shard
def client_read(e164: str, enc_answer):
bucket_records = decrypt(enc_answer) # all records in bucket 11
return next((r for r in bucket_records if r.number == e164), None)Deep Dive 6: Whose Data Is It? Contacts, Consent and Where Data Lives (Week 9 Concepts: Tenant Isolation, Data Residency, Right to Deletion)#
"A reverse phone directory names people who never signed up for it. That's the product, and it's also the argument against it. I'd rather design for that argument than around it: know where every name came from, on what basis, in which country, and be able to change what we show when the law or a court changes its mind."
This builds on Week 9, Day 1 · Tenant Isolation, Week 9, Day 3 · Data Residency and Week 9, Day 4 · Right to Deletion.
Kavya never installed the app
Kavya, a doctor in Kochi, has never used Truecaller, yet a stranger she calls sees "Dr Kavya" before she speaks. She asks to be removed. The same week, a court rules that a user's consent to share their address book isn't the consent of the people in it. What can you do for Kavya within 24 hours, across the store, caches, iPhone shards and phones? What would you need to have recorded years earlier to answer the ruling with a configuration change? And what may you keep about her number?
One strong answer
- Within 24 hours: an unlist request, verified by an OTP to the number, writes a tombstone version with no name; every cache copy is overwritten; the iPhone identity shards drop her at their next rebuild; device lists never held names.
- What you may keep: a spam verdict, if the number had one. Truecaller's published rule is that numbers marked as spam or fraud by the community can't be unlisted; in this design that also means a scammer can't simply unlist their own number.
- The ruling: if every name candidate records its source (owner's profile, verified business, community suggestion, shared contact), basis and region, the response is a policy ("for this jurisdiction, ignore these sources") and a recompute. Without that lineage, the only safe answer is to delete everything.
- Where it lives: jurisdiction is the tenant: EU-contributed personal data stays in the EU partition, Indian data in India; spam verdicts may be served everywhere.
What is published: Truecaller says its store apps don't upload phonebooks "to make them searchable or public" (2022) and that it reads contacts "only on your device" and does "not upload your contacts" (2026); that names come from "suggestions made by other users within the app"; that profiles are private to name searches by default; and that an unlisted number leaves search "within 24 hours", except numbers "marked as spam or fraud by the community". In October 2022 it described "Enhanced Search" as an option for non-EU users "to share data on a consent basis", said EU-originated data is "automatically rejected", that EU users' data is processed in Sweden and that "Indian user data is stored in India". Its privacy policy (3 July 2026) says a user who shares someone else's information confirms they "are aware" and "consent", and that it may process information about non-users' numbers "that we receive from our Users" to derive trust signals.
What critics and courts have said: in 2022, Rest of World published an investigation by The Caravan that reported, from interviews including a former employee, that much of the database came from users' contact lists, including pre-installed apps with Enhanced Search on by default, without the named people's consent; in written responses quoted there, Truecaller said it offers a "privacy-focused service" that is "committed to being transparent and compliant with the laws of the countries we operate in". On 14 September 2026, the Lagos State High Court held, in a case brought for Nigerian non-users, that "Consent to one does not equate to consent to the other", but dismissed the application because the applicants hadn't proved by credible evidence that their rights were infringed; the judge also noted that Truecaller had deleted the applicants' numbers when they asked. Truecaller's lawyers said nothing is uploaded unless a user takes a separate voluntary step, such as installing a version from outside the Google and Apple stores, and that data is processed in India under the DPDP Act (ICIR, September 2026).
The law in India: the DPDP Act was enacted on 11 August 2023 and its Rules notified on 14 November 2025, with an 18-month phased compliance period; consent must be clear and informed and can be withdrawn, rights requests must be answered within 90 days, and failing to keep reasonable security safeguards can cost up to ₹250 crore (PIB, November 2025). As the bill was being passed, Truecaller's then CEO Alan Mamedi said it would "comply with all provisions" and that the law "will not affect the user experience in any significant manner" (Truecaller, August 2023).
%% title: A number's listing, from first call to unlisted stateDiagram-v2 [*] --> UNKNOWN: first seen in a call UNKNOWN --> COMMUNITY_NAMED: trusted suggestions agree UNKNOWN --> REGISTERED: owner signs up and verifies COMMUNITY_NAMED --> REGISTERED: owner signs up UNKNOWN --> SPAM_FLAGGED: score crosses a band COMMUNITY_NAMED --> SPAM_FLAGGED: score crosses a band SPAM_FLAGGED --> UNKNOWN: verdict cleared or disputed SPAM_FLAGGED --> SPAM_FLAGGED: unlist refused COMMUNITY_NAMED --> UNLISTED: owner unlists REGISTERED --> UNLISTED: owner deletes and unlists REGISTERED --> UNKNOWN: number recycled UNLISTED --> [*] class REGISTERED ok class UNLISTED ok class SPAM_FLAGGED bad class COMMUNITY_NAMED queue
- Lineage on every name candidate: source, basis, region, date. The shown name is a function of the candidates and a policy table, so a change in law is a table change and a recompute.
- Jurisdiction as the tenant: personal data partitioned by where it was collected (EU in Sweden, India in India), with separate pipelines and keys; only spam verdicts and verified-business records cross, decided per field.
- Minimise where it's cheap: no names in device lists, SMS spam detection on the phone (Truecaller says SMS content doesn't reach its servers unless shared as feedback), iPhone queries never readable by the server.
- Deletion is a fan-out with a deadline: a tombstone version, overwritten caches, rebuilt PIR shards, candidates deleted from the store and analytics tables, and a log proving each step met the 24-hour promise.
- Let consented sources win, the owner's own name and verified businesses over crowd suggestions: the direction both the DPDP Act's consent rules and the Lagos court's reasoning point. Keep name-to-number search off.
# names/serving_name.py
"""
Choosing the name to show for a number (illustrative). Every candidate carries
its source, basis and region; a per-jurisdiction policy decides which sources may
be shown. Unlisting and recycling are tombstones, not deletions-in-place.
"""
WEIGHT = {"owner_profile": 100.0, "verified_business": 80.0, "community": 1.0}
def serving_name(number, candidates, policy, listing):
if listing.state == "UNLISTED":
return None # tombstone (spam-flagged numbers can't be unlisted)
allowed = policy.allowed_sources(number.jurisdiction) # e.g. {"owner_profile", "verified_business"}
votes = {}
for c in candidates:
if c.source not in allowed or c.collected_at < listing.owner_since:
continue # disallowed source, or about the previous owner
w = WEIGHT[c.source] * c.reporter_trust * c.recency
votes[c.name] = votes.get(c.name, 0.0) + w
if not votes:
return None
return max(votes, key=votes.get)Private lookups and data rights
Q1/3 On an iPhone lookup, what does the Oblivious HTTP relay hide, and from whom?
Answer & explanation
Answer: B
Split knowledge: the relay knows who but not what; Truecaller knows what (encrypted) but not who.
Q2/3 A number marked as fraud by the community asks to be unlisted. What does Truecaller's published policy do?
Answer & explanation
Answer: B
The verdict describes the number's behaviour, and the rule means a scammer can't unlist their way out of it.
Q3/3 A court rules that names suggested by users about non-users need the named person's consent. What makes this a configuration change rather than a rebuild?
Answer & explanation
Answer: B
You can only filter by what you recorded.
Phase 5: Scaling and Edge Cases (5 minutes)#
"Walk me through that number's morning from the platform's side, then the cases that bite."
One Scam Number's Morning#
- 2:00 am · Nightly build
- The UP East base list, version 900, is signed and put on the CDN; Sunita's phone fetches it at 2:40 while charging
- 9:12 am · First calls
- A new number starts dialling down a list; its stream windows start counting callees, declines and call lengths
- 9:40 am · Suspected spam
- 2,100 distinct callees, 80% declined, 40 reports with call proof; version 2 is written to every region, as 8 hot copies
- 10:22 am · Likely fraud
- Fraud reports from trusted reporters ("Fake TRAI call"); every copy is overwritten with version 3
- 10:30 am · Hourly delta
- Delta 910 for UP East adds it; a silent push goes to the region; the iPhone block database rebuilds with it
- 10:41 am · Sunita and Rohit
- Sunita's phone misses on its list and gets "Likely fraud" from the server in about 700 ms; Rohit's iPhone gets a block bit of 1
- 2:00 pm · The number goes quiet
- The caller abandons it; its copies collapse to one; the verdict stays in case it comes back
Edge Cases#
Edge case 1: A spoofed number
Problem: A scam campaign displays a bank's or a real person's number; reports pour in against an owner who made none of those calls.
Solution: If the owner uses the app, their own call log contradicts the reports; verified business numbers have a floor the crowd can't push through. Show "Often spoofed: call back on the official number", not "Spam".
Edge case 2: A recycled number
Problem: A new subscriber inherits a stranger's name or spam label; Truecaller's CNAP comparison calls number recycling "extremely common".
Solution: The new owner's registration starts a new ownership period; older candidates are ignored, and a changed call pattern lowers their weight even before registration.
Edge case 3: A legitimate business turns red
Problem: A hospital's new appointment line is declined by thousands who don't recognise it. Truecaller's Kunal Dua told TechCrunch in 2024 that some legitimate businesses may be blocked temporarily until the community and its algorithms recognise them.
Solution: Business verification, a fast dispute path, "not spam" marks and unblocks as training signals, and removals in the next hourly delta.
Edge case 4: The server is unreachable
Problem: A regional outage, or a phone with no data.
Solution: The device list still blocks known spammers; everything else shows "Unknown number", never a guessed name or a stale "Not spam".
Scaling and edge cases
Q1/2 A bank's number is spoofed by scammers and gets thousands of reports. What should the caller ID show?
Answer & explanation
Answer: C
Spoofing makes the reports true about the calls but false about the number's owner.
Q2/2 The lookup service is down in one region. What does Sunita's phone show for a call from an unknown, unlisted number?
Answer & explanation
Answer: C
Degrade to what the phone knows; never invent reassurance.
Phase 6: Monitoring and Operations#
"Four questions every evening: are verdicts arriving inside the ring, are they fresh, are they right, and are people's data requests being honoured on time?"
Monitoring Dashboard#
Read path
| Metric | Now |
|---|---|
| Lookups | 33,800/s |
| Lookup server p99 | 18 ms |
| Android verdicts inside the 800 ms deadline | 97.6% |
| Hot numbers with copies | 1,240 |
| Busiest Redis node vs median | 1.6× |
| iPhone PIR queries / p99 | 4,100/s / 62 ms |
Write path
- Events into intake: 14,200/s; reports with call proof: 91%; reports rejected: 9%
- Event to new serving record, p99: 3.1 min; Asia to Europe replication lag: 40 s
- New numbers that reached "Likely fraud" today: 3,870; median time from first call: 52 min
Lists and rights
- Phones on today's list: 71%; more than 7 days behind: 6%
- Verdicts reversed after dispute (24 h): 2,300
- Unlist requests (24 h): 18,400; done within 24 h: 100%; oldest open: 6 h
SLOs for the Platform#
SLO 1: A verdict inside the ring
- Target: 97% of Android lookups answered within the 800 ms deadline end to end; lookup server p99 < 30 ms
SLO 2: Private lookups on time
- Target: iPhone block query p99 < 100 ms server time, the goal Truecaller published for in-memory shards
SLO 3: Fresh verdicts
- Target: 99% of score changes on the read path within 5 minutes in the home region and 10 minutes elsewhere; each hourly delta published within 10 minutes of the hour
SLO 4: Mistakes are reversible
- Target: disputes by verified owners resolved within 24 hours; removals in the next hourly delta
SLO 5: Rights honoured
- Target: unlisting effective within 24 hours across store, caches and PIR shards (Truecaller's published promise); data-rights requests answered within the 90 days the DPDP Rules set
Reading the dashboard
Q1/2 "Android verdicts inside the deadline" drops from 97.6% to 88% while the lookup server p99 stays at 18 ms. Where do you look?
Answer & explanation
Answer: B
The server is fast; the deadline is lost on the way. Break the metric down by operator, region and network type.
Q2/2 The busiest Redis node runs at 6× the median. What probably happened?
Answer & explanation
Answer: B
Skew, not volume: one key on one node.
Final Challenge: 1 November, CNAP Goes Live in a Festival Scam Wave#
The interviewer leans back. "One more."
"From 1 November, every Indian operator has to show the caller's registered name on 4G and newer networks by default. That same week, a fraud ring with a SIM farm starts rotating through 5,000 fresh numbers a day, each used for about two hours, with a 'your parcel is held at customs' script. And a bank's real fraud-alert number is being spoofed and mass-reported. What changes in your design?"
- CNAP shows the subscriber's registered name on 4G and higher-generation calls by default from 1 November 2026 (DoT directive, as reported by Inc42)
- A SIM farm rotates through ~5,000 new numbers a day, each live for ~2 hours (assumption)
- One verified bank number is spoofed and gets thousands of reports in a day (assumption)
- Evening lookups at ~2× a normal peak, many from phones in low-signal areas (assumption)
- The hourly delta and an hour-long scoring window were tuned for numbers that live for days
What do you change for the week?
Decide how caller ID and CNAP's name should appear together, how to catch numbers that live only two hours, what to do about the spoofed bank number, how to handle twice the lookups, and what you'd measure.
One strong answer
- CNAP and the verdict answer different questions. The operator's name says whose KYC the SIM is under; the verdict says whether the call is likely a scam, and a SIM-farm number may well show a real person's name. The call screen must make sense when a name is already shown, and never contradict a saved contact.
- Two-hour numbers need two-minute signals. Shorten windows to 5 minutes for new numbers and add features that outlive one number: the number series (users can already block a numbered series on Truecaller), the shared script in report labels ("customs", "parcel"), and the same callees dialled by several new numbers. Score the campaign, then apply it to each new number's first calls.
- Faster deltas where the wave is: 15-minute deltas with the silent push, and iPhone block shards rebuilt on the same cadence.
- The bank: keep its verified name, warn that it's often spoofed, and route the reports to review with the bank, not into its score.
- Load: pre-scale lookup pods and PIR cores for 2×; hot copies and overwrite-never-delete mean verdicts can flip every few minutes without a herd.
- Measure: time from a new number's first call to its first red verdict (target under 15 minutes), the share of a campaign caught in its first ten calls, false-positive reversals, and deadline hits in low-signal regions.
Final challenge · Quest check
Q1/2 CNAP shows the subscriber's registered name by default from 1 November 2026. Why doesn't that replace a spam verdict?
Answer & explanation
Answer: B
Identity and intent are different questions.
Q2/2 Numbers that live two hours defeat an hour-long scoring window. What helps most?
Answer & explanation
Answer: B
Score the campaign, not just the number.
Interview Conclusion#
"Why not ship the whole directory to the phone and skip the server?"
"It's around a terabyte, mostly people's names, which shouldn't sit on millions of phones. Offline, the phone needs only the few hundred thousand numbers most likely to hurt this user, without names: a few megabytes. The server answers the rest."
"Operators now show caller names. Why build this at all?"
"CNAP answers 'whose SIM is this?' from KYC records, with a privacy-friendly design: the receiving operator queries the name and must not keep it. But it doesn't say whether the call is a scam, doesn't cover international numbers, and inherits recycled numbers' old names. The hard system is the one that turns millions of reactions into a verdict within an hour of a new scam starting."
"The single most important decision?"
"Separating the number's behaviour from the person's identity. The spam verdict is about what a number does: it's built from evidence, served everywhere, cached on phones and not unlistable. The name is about a person: it needs a source and a basis for every candidate, it stays in its jurisdiction, and it disappears within a day when they ask. Everything else, the caches, the deltas, the PIR shards, follows from keeping those two apart."
Summary: Concepts Applied from 10-Week Course#
- Week 1: Data at scale
- Partitioning — numbers by hash of E.164, not by prefix
- Replication — the directory copied to each region, read locally, versioned records
- Rate limiting — per-pod token buckets sized by pod count; per-user report and search caps
- Hot keys — scam numbers and helplines written as several cache copies
- Week 4: Caching
- Multi-tier caching — contacts, device list, pod memory, Redis, store
- Cache invalidation — versioned overwrite, signed deltas with removals
- Thundering herd — overwrite instead of delete; single-flight on misses
- Week 8: Analytics pipelines
- Event ingestion — reports and call outcomes with call proof, partitioned by number
- Streaming vs batch — scores in the stream, reporter trust and models in a daily batch
- Week 9: Multi-tenancy and security
- Security architecture — OHTTP relay, Privacy Pass, PIR over homomorphic encryption
- Tenant isolation and data residency — jurisdiction as the tenant; EU data in Sweden, Indian data in India
- Right to deletion — unlisting as a tombstone fanned out in 24 hours
- Week 10: Production readiness
- SLOs for verdicts inside the ring, freshness, reversals and rights
Why Truecaller Matters#
The deadline is a ringing phone
- Caller ID that answers after the user has picked up has failed; the design is shaped by one round trip on the worst network of the user's day
The data is written by its readers, including its attackers
- Every user is a sensor for new scams, and every scammer can pose as a user. Scoring is about weighing evidence and proving a report refers to a real call, not counting votes
Privacy isn't a feature, it's the argument
- A reverse directory names people who never signed up. Truecaller's answers (consent-based sharing outside the EU, unlisting within a day, private lookups on iPhone) sit alongside critics' reporting, a 2026 Nigerian ruling and India's DPDP Rules. A design that records where every name came from can follow the law as it settles
"Precompute the verdict, carry the worst numbers on the phone, and keep every name's origin, so the answer arrives before the second ring and can be taken back when it should be."
CNAP, by the operators (from 1 November 2026)
- What it shows: the name in the originating operator's subscriber records (KYC); businesses may show a registered trademark or GST trade name
- How: the receiving operator queries the originating operator's database of names and must not keep the name after the call ends
- Where: 4G and higher-generation calls, on by default (2G, 3G and landlines to be examined separately); callers with calling line identification restriction are exempt
- What it doesn't say: whether the call is spam or fraud; and, as Truecaller's comparison page points out, names for international numbers or a recycled number's current user
Truecaller, the app
- What it shows: a name from the owner's profile, verified businesses or community suggestions, plus a spam verdict and evidence
- How: a lookup by the app on Android, a private PIR lookup on iPhone, and an on-device list of regional top spammers
- Where: any network the phone can reach, and offline for known spammers; international numbers too
- What it doesn't say: a name for an unlisted number; anything when the server can't be reached and the number isn't on the device list
Self-Assessment Checklist#
After studying this case study, you should be able to:
The read path:
- Budget a lookup against a ringing phone: device tiers first, one round trip, a deadline and a late repaint
- Partition a number directory by hash, replicate it per region, and serve precomputed records
- Protect hot numbers with cache copies, single-flight and versioned overwrites
The write path:
- Turn reports and call outcomes into a spam score that resists brigading (call proof, reporter trust, distinct reporters)
- Split scoring between a stream (fast signals) and a daily batch (reporter trust, models)
- Rate-limit across autoscaling pods with local token buckets sized by pod count
The phone and privacy:
- Ship a regional spam list as a signed base plus deltas, removals included, with a fallback for phones far behind
- Explain how OHTTP, Privacy Pass and PIR hide who asked, prove they're a user, and hide which number
- Keep lineage on every name, partition personal data by jurisdiction, and unlist within 24 hours
Sources#
Truecaller:
- Blog
Ahmed Alnaqah — How We Made Truecaller Finally Work on iPhone (Call Directory Extension limits; OHTTP relay; Privacy Pass tokens; PIR over 8 shards × 21 buckets; two queries per call; sub-100 ms with shards in memory), Truecaller Engineering, 30 June 2025: https://www.truecaller.com/engineering/ios/how-we-made-truecaller-finally-work-on-iphone
- Blog
Shabbir Goher — Controlling QPS in a Fan-Out Microservice Architecture (ad server peaks over 600,000 requests a second; per-pod token buckets of QPS ÷ N; Kubernetes Watch), Truecaller Engineering, 14 February 2025: https://www.truecaller.com/engineering/backend/controlling-qps-in-a-fan-out-microservice-architecture
- Read
Truecaller announces preliminary numbers for the third quarter 2026 (average non-iOS MAU 476.2 million and DAU 411.1 million), 19 October 2026: https://mfn.se/cis/a/truecaller/truecaller-ab-truecaller-announces-preliminary-numbers-for-the-third-quarter-2026-c1f4a15d
- Read
Truecaller interim report January–March 2026 (non-iOS MAU 463.2 million; 500 million+ across iOS and Android; Truecaller Lite for less reliable connectivity), 7 May 2026: https://mfn.se/all/a/truecaller/truecaller-ab-user-and-premium-growth-continue-while-we-strengthen-the-platform-for-the-future-e135255b
- Read
Truecaller corporate site (500M+ monthly active users; 1B+ installs; 68B+ spam calls identified and blocked in 2025), accessed October 2026: https://corporate.truecaller.com/
- Book
Truecaller — About us (founded in Stockholm in 2009 by Nami Zarringhalam and Alan Mamedi; listed on Nasdaq Stockholm in October 2021), accessed October 2026: https://www.truecaller.com/about
- Docs
Truecaller — Spam Blocking (spam lists refreshed daily and specific to the region; automatic updates for Premium, manual for free users; blocking a numbered series; 46.9 billion spam calls identified in 2023), accessed October 2026: https://www.truecaller.com/spam-blocking
- Read
Truecaller — CNAP vs Truecaller ("Likely important" and "Someone you may know" from call patterns; spam list updated daily "because spammers change their numbers frequently"; number recycling), accessed October 2026: https://www.truecaller.com/cnap-vs-truecaller-understanding-the-differences
- Read
Truecaller — Facts and Insights (contacts accessed only on the device, not uploaded; SMS processed on the device), accessed October 2026: https://www.truecaller.com/busting-myths-about-truecaller
- Book
Truecaller — Privacy Center (profiles private to name searches by default; contact requests; unlisting unless identified as a spammer), accessed October 2026: https://www.truecaller.com/privacy/privacy-center
- Read
Truecaller — Privacy Policy (consent for information about others; signals about non-users' numbers received from users; number longevity and trust signals), last updated 3 July 2026: https://www.truecaller.com/privacy-policy
- Blog
Anaida Sen — How Truecaller's Caller ID Works: Your Questions Answered (37.8 billion spam calls in 2021; names from profiles, public partnerships and community suggestions; the most-used label wins), Truecaller Blog, 13 June 2022: https://www.truecaller.com/blog/features/how-truecallers-caller-id-works-your-questions-answered
- Paper
Truecaller — Update on recent inquiries from investors and media (Enhanced Search an option for non-EU users on a consent basis; EU-originated data rejected), 3 October 2022: https://mb.cision.com/Main/20429/3641129/1633543.pdf
- Read
Truecaller — Truecaller statement in relation to new data privacy regulations in India (Alan Mamedi on the DPDP Bill), 8 August 2023: https://news.cision.com/truecaller-ab/r/truecaller-statement-in-relation-to-new-data-privacy-regulations-in-india,c3814927
- Read
Truecaller job listings (Google Cloud, Kubernetes, Cassandra, ScyllaDB, Redis, Aerospike, Kafka, BigQuery; Go and Scala), Greenhouse, October 2026: https://job-boards.greenhouse.io/truecaller
Industry:
- Read
Redpanda — Truecaller halves costs and latency with Redpanda (Kafka clusters in two regions with the most critical in Asia; spam analytics depend on the pipeline), customer case study: https://redpanda.com/case-study/truecaller
Reporting:
- Read
Digital Terminal — Truecaller Surpasses 500 Million Global Users (350 million+ in India, 150 million outside; 50 million+ added in 2025; about 470 employees), 1 April 2026: https://digitalterminal.in/infotainment/truecaller-surpasses-500-million-global-users-built-with-india-at-its-core
- Read
ETV Bharat — India Ranked Fifth Most Spammed Country In The World, Truecaller Report Reveals (68 billion+ spam and fraud calls identified in 2025; India fifth), 6 May 2026: https://www.etvbharat.com/en/technology/india-ranked-fifth-most-spammed-country-in-the-world-truecaller-report-reveals-enn26050604266
- Read
Jagmeet Singh — Truecaller adds a new AI feature to detect and block more spam calls (Max; "multiple dozen algorithms"; Kunal Dua on temporary blocking of legitimate businesses), TechCrunch, 18 March 2024: https://techcrunch.com/2024/03/18/truecaller-automatically-reject-all-spam-calls-android-update
- Docs
Rachna Khaira (The Caravan) — How TrueCaller built a billion-dollar caller ID data empire in India (5.7 billion phone identities per the prospectus; criticism of contact-based data collection and Enhanced Search; Truecaller's written responses), Rest of World, 8 March 2022: https://restofworld.org/2022/how-truecaller-built-a-billion-dollar-id-data-empire-in-india/
- Read
Marcus Fatunmole — Truecaller: Court rejects users' consent for processing non-users' phone numbers (Lagos State High Court ruling of 14 September 2026; application dismissed because infringement wasn't proved; applicants' numbers deleted on request; Truecaller's counter-affidavit), ICIR, 22 September 2026: https://www.icirnigeria.org/truecaller-court-rejects-users-consent-for-processing-non-users-phone-numbers/
- Book
Shrishti Bisht — Centre Directs Telcos To Display Caller Names From November 1 (CNAP on 4G and higher by default from 1 November 2026; originating operator's name database), Inc42, 15 October 2026: https://inc42.com/buzz/centre-directs-telcos-to-display-caller-names-from-november-1/
Law and standards:
- Paper
Press Information Bureau — DPDP Rules, 2025 Notified (Act enacted 11 August 2023; Rules notified 14 November 2025; 18-month phased compliance), 17 November 2025: https://static.pib.gov.in/WriteReadData/specificdocs/documents/2025/nov/doc20251117695301.pdf
- Docs
RFC 9458 — Oblivious HTTP: https://www.rfc-editor.org/rfc/rfc9458
- Docs
RFC 9578 — Privacy Pass Issuance Protocols: https://www.rfc-editor.org/rfc/rfc9578
Design note: the Android lookup path, the cache and store design, the scoring features, bands and code, the list format, the PIR database cadences, the lineage scheme, the dashboards and every number labelled as an assumption or estimate are an illustrative design for the problem, not a description of Truecaller's internal systems beyond what Truecaller and the sources above have published. Calls per user, the share from unknown numbers, report rates, record sizes, list sizes and the festival-week figures are assumptions. Sunita, Rohit and Kavya are fictional. Legal and regulatory points are summarised for engineering context, are not legal advice, and should be checked against current law.
Further Reading#
Related systems to study:
- Read
Google Maps (Bonus Problem 22 in this series): another product whose users are its sensors, with privacy rules on what they send
- Read
Aadhaar (Bonus Problem 3): identity, consent and data protection at India's scale
- Read
WhatsApp (Bonus Problem 2): the phone number as an identity, and end-to-end encryption as a design constraint
- Read
UPI (Bonus Problem 1): fraud at the other end of the scam call
Course material:
- Talk
The course's Week 9 capstone (a multi-tenant SaaS) covers isolation, residency and deletion in a business setting
Foundational concepts (this course):
- Read
Week 1, Day 1 — Partitioning Deep Dive
- Read
Week 1, Day 2 — Replication Trade-offs
- Read
Week 1, Day 3 — Rate Limiting at Scale
- Read
Week 1, Day 4 — Hot Keys and Skew
- Read
Week 4, Day 1 — Caching Patterns
- Read
Week 4, Day 2 — Cache Invalidation
- Read
Week 4, Day 3 — Thundering Herd
- Read
Week 4, Day 5 — Multi-Tier Caching
- Read
Week 8, Day 1 — Event Ingestion
- Read
Week 8, Day 2 — Streaming vs Batch Processing
- Read
Week 9, Day 1 — Tenant Isolation
- Read
Week 9, Day 3 — Data Residency
- Read
Week 9, Day 4 — Right to Deletion
- Read
Week 9, Day 5 — Security Architecture
"Precompute the verdict, carry the worst numbers on the phone, and keep every name's origin, so the answer arrives before the second ring and can be taken back when it should be."
this lesson in shorts · 4
play them all →One idea from this lesson in about a minute each, made for your phone.
- 0/7stages cleared
New lessons by email
New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.
Discussion · …
Public: everyone reading this lesson sees comments. Be kind and keep it on the material.



