How would you enforce a 3-project freemium limit and handle upgrades?

Tests entitlement and growth tradeoffs. Strong answers use API-level enforcement, atomic checks to prevent concurrent overages, soft upsell prompts preserving context, and async billing webhooks. Red flag: UI checks or limits in the projects table.
WHAT THIS TESTS: This question evaluates whether you can build a growth-aware entitlement system rather than a simple database check. Interviewers want to see separation of concerns between billing, application logic, and user experience. They are looking for awareness of race conditions, caching strategies, and how engineering decisions directly impact conversion funnels.
A GOOD ANSWER COVERS: First, an API-level enforcement layer. The limit must be checked in the backend, ideally behind an entitlement service or middleware that answers whether the user can create a project before the creation endpoint runs. Second, atomicity. A simple SELECT COUNT followed by an INSERT is unsafe under concurrency. Strong answers propose a unique partial index, a compare-and-swap on a counter table, or a serialized transaction so that two simultaneous requests cannot briefly create a fourth and fifth project. Third, UX that preserves momentum. When the user hits the limit, the frontend should receive a specific response code or payload that triggers an upsell modal, preserving any draft project name or configuration so the user does not lose context. Fourth, decoupled billing integration. The upgrade flow should emit a webhook from the payment provider that asynchronously updates the entitlement service. Project creation should not query Stripe or PayPal synchronously. Fifth, state definitions. Clarify whether archived or trashed projects count toward the limit, because that changes the query and the user messaging.
COMMON WRONG ANSWERS: Storing the limit as a hardcoded constant in the frontend and disabling the create button there, which is trivially bypassed. Adding an is_paid boolean column directly on the projects table, which couples billing state to application data and breaks when plans change. Returning a generic HTTP 403 with no guidance, which kills conversion. Checking the count in application memory without handling race conditions, leading to silent overages that pollute analytics and support queues.
LIKELY FOLLOW-UPS: How would you handle a team plan where the limit is shared across ten users? What happens if the billing webhook is delayed and the user pays but still sees the limit for thirty seconds? How would you backfill limits for existing users if marketing changes the free tier from three projects to five?
ONE CONCRETE EXAMPLE: Imagine a user clicks Create Project and the entitlement service returns a payload where allowed is false and reason is project_limit_reached. The frontend opens an upgrade modal pre-filled with the draft project title. The user upgrades. The billing system sends a subscription updated event to a queue. The entitlement worker processes it and writes plan pro and project limit ten to the entitlement cache. The user clicks Create again and succeeds. If two requests arrived at exactly the same moment before the upgrade, the database unique partial index on user_id where status equals active would reject the second insert, returning a clean 409 rather than creating an orphaned record.
Source: payproglobal.com
Read the original → payproglobal.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.