Dev Skills
← Follower timeline·Worked solution

Follower timeline — a full worked answer

The whole thing, start to finish, in the order you would actually say it. Read it to learn the shape of a strong answer — then go and produce one yourself, because reading is not the same skill as producing.

core
Common trap
Read this second, not first. Reading a good answer feels like learning and only builds recognition. If you have not tried this problem yet, close this page and write your own answer first. It will feel worse and it is the part that works.

The question

Design the home timeline for a social product. People follow each other; when you open the app you see recent posts from everyone you follow, newest first. Some accounts have tens of millions of followers.

What kind of problem this is

Write-heavy / fan-out. One action by one person creates work for thousands of other people.

The question is always the same: do you do that work when they write, or when everyone else reads? And what do you do about the one user with 200 million followers?

Before anything: where is the slack?

How long the system has before a person notices. Ask this before you draw a single box — it decides more of the design than scale does.

Reads: ~200 ms. Post visibility: seconds. Counts: minutes.

A reader cannot tell when a post was supposed to appear in their feed. So you have seconds of slack on delivery, and almost none on opening the app.

This is the key asymmetry. Opening the app has a hard budget: the timeline must render in a couple of hundred milliseconds or the product feels broken. But readers do not time when someone else's post shows up. If it appears two seconds after it was posted, or ten, the reader cannot tell, because they have no reference clock.

That slack is what makes fan-out on write possible. You can do a large amount of work asynchronously between "they posted" and "it appears in feeds", because readers are not watching that interval. The exception is the author, who knows when they posted and will often refresh at once. Their view of their own post has zero slack and needs a different path.

Counts (likes, replies, views) have minutes of slack. A reader cannot tell 4,102 from 4,118. That is what lets you batch and approximate them instead of writing to a contended row on every interaction.

So: Seconds of delivery slack means you can precompute timelines in the background rather than assembling them on read. Zero slack on the author's own view means you special-case it. Minutes of slack on counts means they never go near the hot path.

1

Requirements

about 5 min

Why this stage exists: Because the question is deliberately vague, and every box you draw before this is a guess.

What you are being asked for: State your assumptions, ask one or two questions that would change a box, and propose the scope.
What a strong answer sounds like

Assumptions: a few hundred million users, global, mostly mobile. Posts are small text plus optional media references. Reads dominate: people open the app far more than they post. Most people follow a few hundred accounts, and a handful of accounts have tens of millions of followers. Auth is solved elsewhere.

First question, and it is the one that changes the most boxes: is the timeline strictly reverse-chronological, or ranked by relevance? Ranked means I cannot simply precompute and append: every read may need scoring, and I need a feature store and a model serving path. I will assume reverse-chronological with a light recency-and-affinity ordering applied at read time, which keeps precomputation viable.

Second question: must a post be visible in followers' feeds immediately, or is a few seconds acceptable? This decides synchronous versus asynchronous fan-out. It sounds like a product detail, but it is the central architectural constraint. I will assume a few seconds is fine.

Scope I propose: post, follow and unfollow, and render the home timeline. Out of scope: ranking models, direct messages, notifications, and media storage. I will treat media as a reference to a separate service rather than pretend it does not exist.

The costs named here
Chronological with light reordering → costs: Engagement will be lower than a ranked feed, and I have given up the ability to surface older-but-better posts. In exchange the timeline can be precomputed, which is what makes reads cheap.
Say this
"Assuming hundreds of millions of users, most following a few hundred accounts, and a few accounts with tens of millions of followers. That skew drives the design. One question: does a post need to appear in feeds instantly, or are a few seconds fine? Because that decides whether fan-out is synchronous. I will assume a few seconds."
Common trap
Asking about the character limit, or whether posts can be edited. Neither changes a box. The follower skew and the delivery deadline are the two things worth asking about.
What an interviewer is ticking off in this stage
  • Stated the follower distribution as skewed: a few accounts have tens of millions
  • Asked whether the timeline is chronological or ranked, and said why it changes the design
  • Asked whether delivery must be immediate or can take seconds
  • Stated that reads dominate writes
  • Proposed scope and named what is out of it
2

Actors and lifecycle

about 6 min

Why this stage exists: Because the happy path is the easy half. The failure branches are where the design actually lives.

What you are being asked for: List the actors — including the system — then write a post's life as a chain of states, and add the failure branches.
What a strong answer sounds like

Actors: the author, the followers who read, and the system. The system makes the decisions that matter: whether to fan this post out or leave it to be pulled, when a post is "delivered", what to do with followers who have not opened the app in months, and when to stop trying.

A post's life: composed → accepted and durable → queued for fan-out → written into follower inboxes → read → possibly deleted.

Failure branches. At accepted: the post is stored but the fan-out event is lost, so it never reaches anyone. That is why the event must be written in the same transaction as the post, via an outbox, rather than published separately. At queued: the author has 30 million followers, so this single job is 30 million writes and will flood the queue and delay everyone else's posts. The branch: above a threshold, do not fan out at all, and let readers pull instead. At writing to inboxes: the job crashes after 8 million of 30 million writes, so it retries and rewrites the first 8 million. That is safe only because inbox writes are idempotent on post id, which makes idempotency a requirement here. At read: a follower's inbox is missing entries because their fan-out is still in flight, so their timeline is briefly short rather than wrong, which is acceptable and worth saying. At deleted: the post is now in millions of inboxes and cannot be deleted from all of them quickly. So deletion is a tombstone plus filtering at read time, and the inbox entries are cleaned up lazily.

A follow also has a life worth a sentence: followed → backfill decision. Following someone does not retroactively insert their old posts into your inbox, because that would be another enormous write job. Their older posts appear via the pull path or are not shown. Saying this out loud is a good signal, because it is easy to overlook.

Common trap
Treating fan-out as one atomic step. It is millions of individual writes that will partially fail, retry, and duplicate. If your lifecycle does not have a branch for "the job died at 8 million of 30 million", you have not thought about it.
What an interviewer is ticking off in this stage
  • Named the system as an actor and listed decisions it makes on its own
  • Wrote the post lifecycle as a chain
  • Handled the lost fan-out event: outbox, in the same transaction
  • Handled the huge-account fan-out as a branch, not as a footnote
  • Required inbox writes to be idempotent because fan-out jobs retry
  • Handled deletion of a post already fanned out: tombstone and lazy cleanup
  • Mentioned what happens on a new follow

The lifecycle, drawn

ComposedauthorStoredsystemQueuedoutbox relayIn inboxesfan-out workersReadfollowersFan-out event lostoutbox in the same transaction, so it cannot be lostAuthor has 30M followersabove threshold, skip fan-out entirely; readers pullWorker dies mid-jobretry; inbox writes idempotent on post idQueue backed uptimelines silently stale; alert on message age, not depthPost deletedtombstone, filter at hydration, clean inboxes lazily
A post's life. Every branch below is a real production failure mode. The fan-out job dying partway through is the one candidates tend to forget.
3

Numbers

about 6 min

Why this stage exists: Because one part of this is hard and the rest is easy, and they look identical until you do the arithmetic.

What you are being asked for: Estimate the read rate, the write rate, and (the number that matters here) the fan-out write amplification. Then finish "So the hard part here is ___."
What a strong answer sounds like

Assume 200 million daily active users. Say each posts 0.5 times a day on average: 100 million posts a day, about 1,000 posts per second, peak maybe 3,000. That is a modest write rate on its own.

Now the amplification, which is the number that matters. Average followers, say 300. Fan-out on write means every post becomes 300 inbox writes, so 1,000 posts a second becomes 300,000 inbox writes a second, peaking near a million. That number drives the design, and it is three hundred times larger than the one you would have quoted if you stopped at "1,000 posts per second".

The tail is worse than the average. One account with 30 million followers posting once produces 30 million writes. At 100,000 inbox writes a second of spare capacity, that single post takes five minutes to deliver and blocks everyone else's posts behind it. The average hides this.

Reads: 200 million users opening the app 10 times a day is 2 billion timeline reads a day, roughly 20,000 per second, peak 60,000. Each read returns maybe 50 posts.

Storage: 100 million posts a day at ~300 bytes is 30 GB a day for posts, which is small. The inboxes are the cost: 100 million posts times 300 followers is 30 billion inbox entries a day, and at ~30 bytes each that is about 900 GB a day, growing with posts times followers. Bound it by capping each inbox at the most recent few hundred entries.

So the hard part here is write amplification, and specifically its tail. Not the post rate, not storage, not read throughput. The design that follows is aimed at the gap between 300 followers and 30 million.

Say this
"A thousand posts a second sounds small, but at 300 followers each that is 300,000 inbox writes a second. And one account with 30 million followers turns a single post into 30 million writes, which at my throughput takes five minutes and blocks everyone behind it. So the hard part here is fan-out amplification at the tail, not the post rate."
Common trap
Reporting 1,000 posts per second and moving on. The post rate is not the workload; the post rate times the follower count is. Missing that multiplication is a very common failure on this problem.
What an interviewer is ticking off in this stage
  • Calculated posts per second
  • Multiplied by average followers to get the real write rate: the amplification
  • Calculated the same figure for the largest account and noticed it dominates
  • Calculated timeline reads per second
  • Noted that inbox storage is bounded by capping inbox length
  • Finished the sentence naming write amplification and its tail

The numbers, drawn

Posts created~1,000 / sec
Timeline opens~20,000 / sec
Inbox writes (fan-out at 300 followers)~300,000 / sec
One post from a 30M-follower account30,000,000 writes

So the hard part is fan-out amplification, and specifically its tail. The average says 300x; the worst case says 30 million, and the worst case is what takes the system down.

The multiplication that is easy to forget. The post rate is not the workload.
4

High-level design

about 12 min

Why this stage exists: Because now — and only now — you have a reason for every box you are about to draw.

What you are being asked for: Draw the design. Justify every box by a requirement or a number, and say how you handle the huge accounts.
What a strong answer sounds like

The core decision is push versus pull. Fan-out on write (push) precomputes each user's inbox when the author posts: reads are one sequential lookup, which is what a 20,000-reads-per-second budget wants, and writes are amplified 300x. Fan-out on read (pull) stores posts once and assembles the timeline by querying everyone you follow at read time: writes are trivial, reads are 300 queries plus a merge, which at 20,000 reads a second is impossible. Neither works alone, so use both.

The hybrid. For ordinary accounts, push: fan out on write into follower inboxes. For accounts above a threshold, say 100,000 followers, do not fan out at all. Their posts stay in their own timeline, and at read time each user's feed is their precomputed inbox merged with the recent posts of the handful of huge accounts they follow. Most users follow only a few such accounts, so that merge is a small, cacheable read. The huge accounts' recent posts are the most cacheable data in the system, because millions of people want the same rows.

Justification by number: the 100,000 threshold comes from the amplification maths. Below it, a post costs at most 100,000 writes, about a second of the fleet's spare capacity; above it, the job would monopolise the pipeline. It is a tunable number, and I would tune it by watching queue latency rather than by picking a round figure and defending it.

The inbox itself: a list per user, keyed by user id, holding post ids and timestamps only. Storing post content there would duplicate every post hundreds of times and make an edit a rewrite of every copy. Capped at the most recent 500 entries, because few users scroll past that and unbounded inboxes are how this design runs out of disk. Partitioned by user id, so one user's timeline is one partition and one sequential read. Held in a store built for this shape: a wide-column store or Redis lists, not a relational table with an index.

The write path: post accepted and written to the post store, with a fan-out event written in the same transaction via an outbox. A relay publishes it. Fan-out workers read the event, look up the author's followers, and write inbox entries in batches, idempotent on post id, because these workers will retry. Justified by the Stage 2 branch where a job dies partway through.

The read path: timeline service reads the user's inbox (cached), merges in recent posts from the large accounts they follow (also cached), sorts, then hydrates post ids into full posts from a post-content cache. Hydration is a batch get, not 50 individual lookups.

The author's own post: written to their own inbox synchronously, before the response returns. This costs one extra write and removes a common complaint in this kind of product: posting something and not seeing it.

Counts (likes and replies) do not live in the post row. They are batched and aggregated separately, because a popular post would otherwise turn into a single contended row taking thousands of increments a second.

The costs named here
Hybrid push and pull → costs: Two code paths for the same feature, forever, plus a threshold that needs tuning. Timelines are assembled from two sources, so ordering and pagination need care across the boundary.
Inbox stores ids, not content → costs: Every read needs a hydration step, so a timeline is two round trips instead of one. In exchange, editing or deleting a post does not require rewriting millions of inbox rows.
Capping inboxes at 500 entries → costs: Deep scrolling has to fall back to the pull path, which is slower. Few users scroll that far, so I am optimising for the common case.
Common trap
Picking push or pull and defending it to the end. The interviewer is waiting for you to notice that the follower distribution makes one answer wrong at each extreme. The hybrid is the answer; the threshold is the interesting part.
What an interviewer is ticking off in this stage
  • Chose a hybrid and explained why neither push nor pull works alone
  • Named a follower threshold and justified it with the amplification number
  • Inbox holds post ids, not post content, and is capped
  • Inbox partitioned by user id so a timeline read is one partition
  • Fan-out event written via an outbox in the same transaction as the post
  • Fan-out workers are idempotent on post id
  • Author's own post written synchronously to their own inbox
  • Post hydration is a batch lookup, not N lookups
  • Counts kept off the post row

The architecture, drawn

one txnrelaypush pathpull pathAuthorPost svcPosts + outboxFan-out queueFan-out workersidempotentInboxesby user id, cappedReaderTimeline svcmerge + hydrateBig-account cacherecent posts
Two paths that meet at the timeline service. Ordinary accounts are pushed into inboxes; huge accounts are pulled at read time and cached hard.
Authorposts onceFan-outInbox 1Inbox 2Inbox 3
one write inmany writes out — reads later are free
One write in, many writes out. Expensive writes buy cheap reads, until one author has 30 million followers.
5

Deep dive and tradeoffs

about 14 min

Why this stage exists: Because naming what a decision costs is the single clearest signal of seniority there is.

What you are being asked for: Go deep on the two hardest parts. Name a cost after every decision.
What a strong answer sounds like

Hard part one: the huge-account path, in detail. Above the threshold nothing is fanned out, so at read time I need the recent posts of every large account a user follows. I keep a per-author recent-posts list, capped at the last few hundred, and cache it aggressively. This is the highest-value cache in the system: a large account's recent posts are requested by millions of users, so the hit rate approaches 100% and one row serves a huge amount of traffic. Cost: a reader sees a new post from a large account only once that cached list refreshes, so its visibility depends on the cache TTL rather than on fan-out. I accept that, because the alternative is a five-minute fan-out.

Merging the two sources correctly is the fiddly part. The inbox is ordered by time; so are the pulled lists. I merge and take the top N by timestamp. Pagination cannot be an offset, because the two sources shift independently. It has to be a cursor holding the timestamp and post id of the last item shown, and the next page asks both sources for items older than that cursor. Cost: a cursor is more work than an offset and cannot express "jump to page 5", which nobody wants in a feed anyway.

Hard part two: fan-out backpressure. If the queue backs up, timelines silently get stale, and because nothing errors, the team may not notice until users complain. Defences: separate queues by priority so a celebrity backfill cannot delay ordinary posts; alert on queue age rather than queue depth, because age is the number that maps to user pain; and scale workers off backlog growth rather than absolute size. If it backs up badly, I would drop fan-out for inactive users (someone who has not opened the app in 60 days does not need their inbox maintained in real time) and rebuild their timeline via the pull path when they return. Cost: a returning user's first load is slower, which is a good trade against delaying everyone else.

Inactive users are worth raising unprompted. Maintaining inboxes for accounts that never open the app can be a large fraction of total fan-out work, and it produces nothing. Skipping them is one of the biggest savings available here, and the cost is the slower first load above.

Hot key on read: a very large account's recent-posts row is read by millions. One cache key means one hot node. I would replicate that key across several cache nodes with a suffix and read a random one. Cost: N times the memory for those keys and slightly more complex invalidation. Both are small, since there are only a few thousand such accounts.

Consistency, said per feature: the timeline is eventually consistent and may be seconds behind, which is fine and matches how users perceive it. The author's own post is read-your-own-writes, guaranteed by the synchronous self-write. Like counts are eventually consistent and approximate. Follows are strongly consistent, because unfollowing someone and still seeing their posts is a trust problem, not a latency one. So the follow write is synchronous, and the read path checks the follow graph before showing inbox entries.

Deletion, revisited: a deleted post is tombstoned, filtered at hydration time, and its inbox entries are removed lazily. Cost: for a few seconds the post id sits in inboxes and is filtered out on read, which means a timeline can return 49 items instead of 50. Acceptable, and much cheaper than chasing millions of rows synchronously.

The costs named here
Skip fan-out for users inactive 60+ days → costs: Their first load on return is slow, built from the pull path. Saves a large share of total fan-out work.
Replicate hot cache keys across nodes → costs: More memory and a fanned-out invalidation, for the few thousand accounts where one key would otherwise be a hot spot.
Tombstone deletes instead of purging inboxes → costs: A timeline page can briefly come back one item short. Far cheaper than millions of synchronous deletes.
Say this
"Above 100,000 followers I stop fanning out and pull at read time instead, because one post from a 30-million-follower account is 30 million writes that would block the queue for five minutes. Cost: their posts reach readers through a cached list rather than being pushed, and I now maintain two paths forever. I would tune that threshold on queue age, not pick a round number."
Common trap
Going deep on the database schema and never mentioning what happens when the fan-out queue falls behind. Stale timelines fail silently: no errors, no alerts, just a product that stops showing new posts.
What an interviewer is ticking off in this stage
  • Detailed the large-account read path and why its cache hit rate is near perfect
  • Merge and cursor-based pagination across two sources
  • Fan-out backpressure: priority queues, alert on age not depth
  • Skipping fan-out for inactive users, and the cost of doing so
  • Handled the hot cache key with key replication
  • Gave a per-feature consistency answer rather than one blanket statement
  • Handled deletion through tombstones and lazy cleanup
  • Named a cost after every decision

The decision this turns on

A
Fan-out on write (push)
  • —Reads are one sequential lookup of a precomputed list. Fast and cheap.
  • —Write cost is multiplied by the follower count.
  • —A huge account turns one post into millions of writes and blocks the queue.
  • —Storage grows with posts times followers, not with posts alone.
B
Fan-out on read (pull)
  • —Writes are trivial: store the post once and stop.
  • —Reads query every account you follow and merge, which is hundreds of lookups.
  • —Perfect for huge accounts: their posts are read by millions, so cache hit rates are near 100%.
  • —Hopeless at 20,000 timeline opens a second for ordinary users.
Which one: Push for ordinary accounts, pull for the few above a follower threshold, and merge the two at read time. The threshold is the main design decision, and it should be tuned by watching fan-out queue age rather than picked as a round number.
Push versus pull. The reason the answer is "both" is the follower distribution.

And then they push back

A complete answer is where the interview starts, not where it ends. These are the questions this design invites — the interview is decided here far more often than in the diagram.

One user has 200 million followers. What happens?
A weak answer

"The fan-out would be slow but it would eventually finish." It would, and by then the post is hours old and every other user's timeline was blocked behind it. Treating this as a latency inconvenience rather than a systemic failure misses the point.

A strong answer

"One post becomes 200 million writes. At my fan-out capacity that is minutes, and worse, it occupies the pipeline so everyone else's posts are delayed behind one person's. So above a follower threshold I stop fanning out entirely — their posts stay in place and readers pull them at read time, merged with their normal inbox. That works because a huge account's recent posts are the most cacheable data in the system: millions of people want the same few rows, so the hit rate is near perfect. Cost: two code paths for one feature forever, a threshold that needs tuning, and timelines assembled from two sources so pagination needs a cursor rather than an offset."

10x traffic tomorrow. What fails first?
A weak answer

"We would add more servers and scale horizontally." This says nothing. Every system has a first thing to break, and not naming it means you do not know your own design well enough to have found it.

A strong answer

Name a specific component and the reason. "The stateless services scale out fine, so they are not the constraint. The database primary is, because every write goes through one machine and I am at roughly 40% of its write capacity now. So at 4x I am at the ceiling, and I would see write latency climb and connection pool waits before anything errors. The second thing is the cache, because 10x reads on a fixed working set is fine but 10x on a growing key space means my hit rate drops and the misses land on the database that is already the constraint. In order: database writes, then cache hit rate, then the connection pool. And I would find out from p99 write latency, not from errors."

The queue is backed up by an hour. What do you do?
A weak answer

"Add more consumers." Sometimes right, sometimes wrong: if consumers are failing and retrying, adding more accelerates the failure. Not distinguishing the two cases is the tell.

A strong answer

"First I work out which of two things it is, because the fixes are opposite. If consumers are healthy and simply outnumbered, scale them out, and check what they are hitting, because often the ceiling is a database and adding workers just moves the queue there. If consumers are failing and retrying, more consumers makes it worse; I find the poison message or the broken dependency and stop the retry storm. Then I triage rather than drain in order: if urgent and non-urgent work share a queue, the non-urgent is now delaying the urgent, which is a lesson for the design, not just the incident. Then I decide what to drop — an hour-old 'your driver is arriving' notification is worse than useless and I would discard it rather than deliver it. And afterwards I alert on message age and backlog growth rate, not depth, because depth crossing a threshold tells you an hour too late."

A user updates their profile and immediately sees the old version. How do you fix it without slowing everyone down?
A weak answer

"Make all reads go to the primary." That works, and it throws away the benefit of having replicas. You have solved one user's problem by removing your read scaling.

A strong answer

"Scope the fix to the user who wrote. Options in increasing precision: pin that session to the leader for a short window after a write, which is simple and costs a little leader load; or record the write position at commit and have their subsequent reads wait for a replica that has reached it, which is exact and means reads can block briefly; or pin them to a single replica so they at least see a monotonic view, which is cheap and prevents flickering between old and new. For a profile edit specifically, the cheapest correct answer is to render the response from the write itself rather than re-reading — the server already knows the new state. Everyone else continues reading from replicas with normal lag, which is invisible to them because they were not the one who changed it."

Why a queue instead of just calling the service directly?
A weak answer

"Queues decouple services and are more scalable." True in the abstract and it does not explain why this particular call needs one. Queues also add duplicate delivery, ordering questions and debugging difficulty, and not mentioning those means you added one by reflex.

A strong answer

"Because the user does not need to wait for this work to finish, and because the downstream cannot absorb my spikes. Concretely: sending the receipt email takes 800 ms and can fail, and there is no reason to make the customer wait for it or to fail their order because a mail provider is down. The queue absorbs a 10x spike into a longer backlog instead of a broken service. What it costs: the customer gets 'received' rather than 'done', so the product has to be honest about that; every consumer must be idempotent because delivery is at-least-once; and a failure now happens in a worker minutes later, which is harder to debug. I would not queue the stock check or the payment, because the user needs a real yes-or-no answer before they leave the page. A queue there adds latency and buys nothing."

What does this cost per month?
A weak answer

"I am not sure, it depends on the provider." Interviewers rarely want exact prices. They want to know whether you understand which line item dominates, and that is answerable from your own estimates.

A strong answer

"I would not guess at prices, but I can tell you the shape, which is what matters. The dominant cost here is egress bandwidth: my estimate said 1,350 GB a second to viewers, and at that volume nothing else on the bill comes close. Second is storage, because I keep raw data for two years and it grows every day whether anyone reads it or not. Third is compute, which is elastic and therefore the easiest to control. That ordering tells me where to spend engineering effort: a percentage point of CDN hit rate is worth more than any amount of compute optimisation, so cache hit rate is the number I would put on a dashboard and defend. Storage cost I would attack with lifecycle policies moving cold data to cheaper tiers and with tiered granularity. Compute I would run on interruptible capacity because the work has hours of slack."

Which alert fires first when this system starts failing?
A weak answer

"We would alert on CPU and memory." Those are symptoms of the machine, not of the user experience, and they fire either far too late or constantly for no reason.

A strong answer

"I would alert on user-visible symptoms first and causes second. The first alert should be error rate or latency at p99 on the main user path, because that is what maps to someone being unhappy. Then a small number of leading indicators that fire before users notice: queue age (not depth), because age maps directly to how late the work is; replication lag; cache hit rate dropping, which precedes database saturation; and connection pool wait time, which is the earliest sign of a slow dependency. For this system specifically, the earliest signal is usually cache hit rate, because everything downstream is sized assuming the cache absorbs most reads. I would not page on CPU. It is a dashboard metric, not an alert, because a machine at 90% CPU serving requests fine is not an incident, and a machine at 20% CPU timing out is."

Now make it multi-region.
A weak answer

"We would replicate the database to another region." That covers reads. The question is what happens to writes, and that is where the design actually changes.

A strong answer

"Reads are the easy half: replicas in each region, users read locally, and they may be slightly stale — I would say how stale and which reads cannot tolerate it. Writes are the hard part, and there are three options. Single write region: simplest, no conflicts, and users far away pay 150 ms per write, which is fine if writes are rare and unacceptable if they are not. A home region per data partition, so each user's data lives near them: local writes without conflicts, but it needs a routing layer plus a plan for users who move. Multi-leader with conflict resolution: local writes everywhere, and now I own a conflict problem, which is only acceptable where merges are safe. I would pick the second for user-owned data and single-region for anything globally contested like inventory. What changes elsewhere: cross-region replication cost, a failover story per region, and data residency rules that may force certain data to stay in certain places. I would ask about that constraint rather than discover it later."

The topics this answer used

Now do it yourself

You have read a good answer. That is worth very little until you can produce one on a blank page — so go and write this problem without looking, and compare afterwards.