The Google L4 Interview Question I Was Nailing (Until I Tried To Make Consistency Global)
Photo by Adarsh Chauhan on Unsplash

Sometimes I look back and imagine where life would be if I had cleared this interview early last year.

Probably not so fun ;)

Free to read for non members.

The question sounded easier than it was

Last year, in a Google L4 interview, I got a system design question that looked harmless at first.

Design a global study platform.

We had a small discussion about the requirements.

Students can publish notes.
They can comment in threads.
They can follow other students.
They can search content.
They can like posts.
They can buy tutoring credits and spend them on sessions.

I was like…cool.

At first, I relaxed a little.

Not because it was easy, but because it was familiar.

I had seen versions of this system before.

It was part social network, part content platform, part marketplace.

So I started drawing the usual boxes.

Clients. API gateway. Services. Databases. Queues. Search index. Feed workers. Caches. Replicas in multiple regions. Yk….

The interviewer nodded through most of it.

In the last 15 mins,

he asked the question that changed the room:

What consistency model does this system need?

I remember thinking, “Great. I know this.”

I said something like,

“We can design the platform to be strongly consistent where needed, but eventually consistent for the rest.”

And that was not wrong.
But, maybe it was too soft.
It sounded like a slogan instead of a design.

The interviewer pushed: “Where exactly does each guarantee live? Can you be more specific? We don’t have much time. Do you have a router? What does the router do? What does a request carry? What invariant are you protecting?”

And that is where I fumbled.

I had not made the system choose between them with enough precision.

That distinction matters.

In distributed systems interviews, saying the right words is the warm-up.

The real question is whether you can attach each word to an invariant, a data path, and a failure mode.

The mistake was thinking consistency is a property of the whole app

The trap in this question is that “global study platform” sounds like one system.

But it is not one consistency problem.

It is four different consistency problems wearing the same product name.
A tutoring wallet is not like a like count.
A threaded reply is not like a search index.
A user’s own profile read is not like a recommendation feed.

Each part of the product has a different tolerance for being stale, reordered, duplicated, or temporarily wrong.

That is the point of the question.

The interviewer does not want a religious answer like “use strong consistency” or “use eventual consistency.”

They want to see whether you know where correctness actually lives.

For a global study platform, I should have framed the system around a consistency policy router.

Every request comes through the API gateway. Before it hits storage, it is classified by the invariant it touches.

Clients
|
API Gateway
|
Consistency Policy Router
|------------------|------------------|------------------|------------------|
Strong Store Causal Log Session Store Eventual Stores
Ledger DB Comment Events User Read Tokens Feed/Search/Likes
Raft/Paxos Lamport clocks Sticky/session Async replicas
Serializable dependency IDs consistency CRDT/caches

This is the answer I wish I had given.

The platform deliberately uses multiple consistency models because different features protect different truths.

Strong consistency belongs to money

The tutoring credit system is the easiest part to reason about because the invariant is brutal.

Do not spend the same credit twice.

If a student has $10 of tutoring credit, two regions cannot both decide that the same $10 is available. A stale balance is not just awkward UX.

It becomes real financial damage.

So tutoring credits, purchases, refunds, and tutor payouts belong in the strongly consistent path.

The rule is:

Every read of a wallet balance must reflect the latest committed write.

That means the wallet should not be modeled as a loose counter spread across regions. It should be a ledger.

Purchases create immutable credit entries. Spending creates debit entries. Refunds create reversal entries.

Payouts are derived from committed session records. The visible balance is derived from the ledger, not guessed from a cache that may or may not have caught up.

Writes go through a single leader or a quorum protocol like Raft or Paxos. Transactions are serializable. If two sessions try to spend the same funds, one wins and the other fails or retries against the new balance.

This path is slower and less available than a local eventually consistent write.

That is fine.

Money-like data earns the cost.

The interview answer should explicitly name the invariant:

Invariant: available_balance >= 0
Invariant: one committed credit cannot be consumed twice
Consistency: strong / serializable
Storage: ledger database

That is much stronger than saying, “Payments should be strongly consistent.”

Payments are not strongly consistent because they are important in some vague emotional sense. They are strongly consistent because the system has a global invariant that must survive concurrency, retries, failover, and regional lag.

Causal consistency belongs to conversations

Comments are different.

A comment thread does not need every user in every region to see the absolute latest comment at the same instant. That would be expensive and unnecessary.

But it does need cause and effect to remain intact.

If I see a reply, I should be able to see the comment it replies to. If I see an edited comment, I should not see the edit before the original comment exists. If I receive a notification about a reply, clicking it should not take me to an empty thread where the reply has not arrived yet.

This is where causal consistency fits.

The rule is:

If event B depends on event A, everyone must observe A before B.

For the study platform, posts and comments can be stored as events in an append-only causal log.

Each event carries dependency information.

Post created: P1
Comment created on P1: C1 depends_on P1
Reply created to C1: R1 depends_on C1
Comment edited: E1 depends_on C1

A replica may receive R1 before C1 because networks enjoy making interview candidates sweat. But the replica should not expose R1 until C1 and P1 are visible.

Lamport clocks can help provide a logical ordering. Vector clocks can carry richer causality when multiple writers and regions are involved. Dependency IDs are often enough when the application has clear parent-child relationships, such as post to comment to reply.

The important part is not the clock name.

The important part is the visibility rule.

Do not show dependent events before their causes.

In the interview, I mentioned causal consistency, but I did not say enough about the read path. The better answer would be:

When a user requests a thread, the comment service reads from the causal log or a materialized thread view. Before returning events, it checks whether all dependency IDs are present in the view. If a child event has arrived before its parent, the child is buffered, hidden, or the read is routed to a replica that has the dependency chain.

That sentence turns a concept into a system.

Read-your-own-writes belongs to personal UX

Some consistency problems are not about global correctness. They are about trust.

Imagine Asha updates her display name. The write succeeds. She refreshes her profile and sees the old name.

Technically, nothing catastrophic happened. Other replicas will catch up soon. No money was lost. No comment thread was corrupted.

But from Asha’s point of view, the app feels broken.

This is where read-your-own-writes consistency matters.

The rule is:

A user must always see their own latest writes, even if other users may briefly see an older version.

This is perfect for profile edits, profile photos, settings, saved drafts, and maybe recently published notes in the author’s own dashboard.

The implementation is usually not mystical.

After a successful write, the backend returns a version token.

Asha updates display name to "Asha"
Write returns version: profile:v42
Asha refreshes profile with token profile:v42
System guarantees v42 or newer

The client includes that token on future reads. The router then sends the request to a replica that has applied at least v42. If no nearby replica has caught up, the request can fall back to the primary or serve the fresh value from a short-lived session cache.

Other users may still see profile:v41 for a few seconds.

That is acceptable because the invariant is scoped to the writer’s session. The system is not promising that everyone sees the latest profile immediately. It is promising that the person who made the change will not be gaslit by their own refresh button.

This was one of the places where my interview answer had the right label but not enough mechanics.

I said, “Use read-your-own-writes for profile updates.”

I should have said:

Write response includes version token
Subsequent reads include token
Router selects replica >= token or falls back to primary
Session cache may serve fresh user-owned data briefly

Again, the difference is precision.

Eventual consistency belongs to feeds, likes, and discovery

The largest part of the platform should probably not be strongly consistent.

Feeds, search indexes, recommendations, trending notes, follower counts, analytics dashboards, and like counts can tolerate temporary disagreement.

If one region shows 97 likes and another shows 101 for a short period, nobody loves it, but the product survives.

If a newly published note takes a few seconds to appear in search, that is usually acceptable.

If a recommendation feed is slightly behind, the user probably cannot tell.

These features should use eventual consistency because they benefit from speed, regional availability, and asynchronous fanout.

The rule is:

Replicas may disagree temporarily, but they converge over time.

The implementation is a familiar pipeline.

Writes publish events to a durable queue. Background workers update feed caches, search indexes, counters, recommendation stores, and analytics systems. Likes can use conflict-friendly structures such as CRDT counters or idempotent per-user like records that are aggregated asynchronously.

The product can also choose different freshness targets.

Search indexing might aim for seconds. Analytics might be minutes. Recommendations might be rebuilt in batches. Feed fanout might be immediate for followers in the same region and delayed elsewhere.

The key is that temporary staleness is not a bug in this part of the system. It is a tradeoff.

That tradeoff should be explicit.

Like count / feed / search -> eventual consistency
Correctness invariant -> convergence, idempotency, no permanent loss
UX strategy -> optimistic updates, stale markers, background repair

For example, when a student likes a note, the UI can optimistically show the like immediately for that student. The event is written to a queue. Workers update counters and feed ranking.

If duplicate like events arrive, idempotency keys prevent double counting. If regions disagree briefly, reconciliation fixes the count.

This is where eventual consistency shines.

Not because correctness does not matter, but because immediate global agreement does not matter.

The policy router is the part I should have emphasized

The clean answer is not four separate storage systems casually glued together.

The clean answer is a router that makes consistency a deliberate product decision.

Each request is classified by the data type and invariant.

Wallet balance          -> Strong consistency
Comment dependency -> Causal consistency
User's own profile read -> Read-your-own-writes
Like count / feed -> Eventual consistency

That router does not have to be a fancy independent service. It can be a logical layer inside the API gateway or service boundary.

The important thing is that requests carry the information needed to enforce the right guarantee.

A wallet spend carries an idempotency key and hits the ledger.

A comment reply carries depends_on.

A profile read carries a session version token.

A like event carries a user ID, target ID, event ID, and timestamp so workers can deduplicate and reconcile.

Once you say it that way, the architecture becomes easier to defend.

You are no longer waving at consistency as a general concern. You are mapping each feature to the cheapest consistency level that preserves its invariant.

That is the mature answer.

Strong consistency is not the default because it is expensive and can reduce availability.
Eventual consistency is not the default because it can violate ordering and financial correctness.
Causal consistency is not a replacement for serializable transactions.
Read-your-own-writes is not a guarantee for everyone. It is a scoped promise to the writer.

Each model has a job.

But, it’s fine.
I built a lot of systems properly after that interview haha! At least now the mistake has a useful shape.

In case we are meeting for the first time, come over here, it’ll be worth the roller coaster of articles that are gonna come up in the next few weeks.

If you’re an established writer, here are the brands paying for sponsored articles.

I do not use AI in my writings and you shouldn’t either.

So, How did I go from 0 to 1000 here ?