Design the data model and backend for a 7-day trial at scale

Tests state machine design for time-bound entitlements at scale. A strong answer covers: an idempotent enrollment API, a trial ledger with timezone-aware expiration, and an event-driven expiration pipeline.
What's really being asked
Your ability to design a stateful, time-bound entitlement system for high scale. The interviewer cares about data model precision, race-condition handling, and whether you treat trials as first-class subscription states rather than simple boolean flags.
The full answer
First, the data model. You need a users table, a trials table with trial_id, user_id, started_at, ends_at, timezone, status (trialing, converted, expired, cancelled), and source metadata. You also need an entitlements or features table that resolves access by checking active trials or paid plans. Second, enrollment logic. On signup, an idempotent enrollment service creates the trial record and publishes a TrialStarted event. If the user already has a trial, the service returns the existing record to avoid duplicates. Third, expiration and state management. Instead of a global cron job scanning millions of rows, use a delayed message queue or a scheduler that enqueues an expiration task per user at their exact ends_at timestamp. Alternatively, shard time-bucketed workers, but delayed events are preferred. Fourth, conversion and billing integration. When the trial ends, the entitlement service checks for a paid subscription. If none exists, revoke access and emit TrialExpired. If the user converts, Stripe sends webhook events like invoice.paid or customer.subscription.updated, which your service consumes to update status to active and extend entitlements. Fifth, edge cases. Handle early cancellation by setting status to cancelled with a cancelled_at timestamp. Handle timezone awareness so a user who signs up at 11 PM does not lose hours. Handle clock skew by using database timestamps or vector clocks rather than client time.
The mistakes people make
Proposing a single nightly cron job that queries every user to check trial end dates. This does not scale past a few hundred thousand users and creates a thundering herd. Storing trial state only in Redis without a persistent source of truth. Using client-side clocks to determine expiration. Forgetting idempotency so double signups create duplicate trials. Ignoring the endowment effect mechanics by not having a downgrade hook that explicitly removes the feature, which is the actual product lever.
What usually comes next
How would you change this if the trial length were variable per user cohort? How do you handle a user who signs up, cancels immediately, then signs up again to get another trial? How would you support in-app purchases where Stripe is not the source of truth? What metrics would you log to measure the endowment effect?
A concrete example
A user signs up at 2024-01-01 09:00 PST. The enrollment API writes a trial record ending at 2024-01-08 09:00 PST and publishes a delayed SQS message visible at that exact time. The entitlements service returns true for the premium feature because status is trialing. On expiration, the worker consumes the message, sees no paid subscription, sets status to expired, and emits an event that triggers an in-app downgrade modal highlighting what the user is about to lose.
Interview question
Which expiration pipeline best prevents a thundering herd when handling millions of trials at scale?
- a.Use sharded time-bucketed workers to scan trials ending within hourly windows
- b.Store trial state exclusively in Redis and rely on TTL keyspace notifications
- c.Run a nightly cron job that queries every active trial to check for expiration
- d.Enqueue a delayed message per user that becomes visible exactly at their ends_at timestampCorrect
Why? this is the answer
Delayed messages per user avoid scanning millions of rows and eliminate thundering herds. C is tempting because it shards work, but the card explicitly prefers per-user delayed events, while A and B are common anti-patterns.
Just read this? Test yourself on what you have been reading.
Read the original → docs.stripe.com
- #growth
- #system design
- #subscriptions
- #entitlements
- #stripe
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on growth — each one lists the topics its interview covers.
See open roles