DEV Community

Cover image for Technical Article
Karan Kumar
Karan Kumar

Posted on • Originally published at blog.duolingo.com

Technical Article

How Duolingo Scales to 100M+ Users: The Architecture of Gamified Learning

In this guide, we explore architecture. Your app is booming. You've hit 10 million daily active users. Suddenly, the "streak" logic—the very mechanism keeping users engaged—starts lagging. A 500ms delay in updating a user's progress isn't just a latency spike; it's a psychological blow that disrupts the dopamine loop. This is the nightmare of scaling a gamified system.

Table of Contents

Scaling a language app isn't as simple as throwing more pods at a Kubernetes cluster. It requires managing high-write workloads (where every single tap is an event), maintaining strict consistency for competitive leaderboards, and delivering personalized AI content in milliseconds.

Here is how to architect a system that handles the scale of Duolingo without collapsing under its own weight.

The Challenge: The "High-Write" Paradox

Most social apps are read-heavy; you scroll a feed or read a post. A learning app is fundamentally different. Every single interaction—a correct answer, a missed word, a timed challenge—is a write operation.

When you have 100 million users, you aren't dealing with a few thousand requests per second; you're dealing with a tidal wave of state updates. If every user action triggers a synchronous write to a relational database, your DB will lock up faster than a junior dev on their first day of on-call.

Furthermore, there is the "Streak" problem. A streak is a global state that must be accurate across all devices. If a user finishes a lesson on their iPad, their Android phone must reflect that streak immediately. Any inconsistency here leads to a support ticket and a frustrated user.

The Macro Architecture: From Monolith to Event-Driven

To survive this load, you cannot rely on a single giant database. You need a decoupled, event-driven architecture that moves the heavy lifting away from the request-response cycle.

Instead of the traditional User Action \rightarrow Update DB \rightarrow Return Success flow, you move to:
User Action \rightarrow Emit Event \rightarrow Async Processing \rightarrow Eventual Consistency.

architecture diagram

Deep Dive: The Gamification Engine

Let's discuss leaderboards. A global leaderboard with millions of users is a computational nightmare. Running SELECT SUM(xp) FROM users ORDER BY xp DESC LIMIT 10 every time a user opens the app is a recipe for a database meltdown.

The Redis Sorted Set Strategy

To solve this, we use Redis Sorted Sets (ZSET). In a ZSET, every element is mapped to a score. Redis maintains these in a skip-list, allowing O(logN)O(\log N) insertions and range queries.

  1. Sharding the Board: Rather than placing 100M users in one set, we shard them into leagues (Bronze, Silver, Gold). This limits the size of each ZSET, keeping latency low.
  2. Write-Behind Caching: When a user earns XP, we update the Redis score immediately. The update to the permanent database happens asynchronously via a Kafka consumer. This ensures the user sees their rank jump instantly (low latency) while the system of record remains durable.

The AI Layer: Moving from Rules to LLMs

Old-school language apps relied on hard-coded decision trees: "If user misses 'Apple' three times, show them the 'Fruit' vocabulary list." This approach is brittle, boring, and doesn't scale.

Modern architecture integrates Generative AI into the core loop. However, you cannot wrap a GPT-4 API call around every sentence—the 3–5 second latency would kill the user experience, and the cost would bankrupt the company.

The Hybrid AI Workflow

To make AI feel instantaneous, we use a tiered approach:

  • Tier 1: The Cache (Deterministic): Common mistakes and standard corrections are stored in a distributed key-value store. If a mistake is common, the response is returned in <10ms.
  • Tier 2: Small Language Models (SLMs): For basic grammar corrections, a fine-tuned, smaller model (such as a distilled Llama or Mistral) runs on internal GPU clusters. This provides an ideal balance of speed and intelligence.
  • Tier 3: The LLM (Reasoning): For complex "Explain why this is wrong" requests, the system routes the query to a heavy-duty LLM.

sequence diagram

Data Persistence: Choosing the Right Tool

A "one size fits all" database approach leads to systems that are slow and impossible to migrate.

1. The User Profile (Document Store/NoSQL)
User settings, preferences, and progress snapshots are often unstructured. Using a document store like MongoDB or DynamoDB allows the schema to evolve as new features are added without requiring a massive migration of a billion rows.

2. The Streak & Ledger (Distributed SQL)
Streaks and currency (Gems/Lingots) require ACID compliance. You cannot "eventually" be correct about whether a user spent their last 10 gems. Here, we use Distributed SQL (such as CockroachDB or Spanner). These provide the scale of NoSQL with the consistency of Postgres, using the Raft consensus algorithm to ensure data is replicated across regions without conflicts.

3. The Analytics Lake (Columnar Store)
To understand where users drop off in a lesson, we must analyze billions of events. Row-based databases are inefficient for this. We pipe Kafka events into a columnar store (like ClickHouse or BigQuery), allowing us to aggregate data across millions of users in seconds.

The Trade-offs: The Cost of Performance

No architecture is perfect; every decision involves a trade-off.

Latency vs. Consistency
By using an event-driven model for XP updates, we accept "Eventual Consistency." For a split second, a user's profile page might show 1,200 XP while the leaderboard shows 1,250. In a banking app, this is a disaster; in a language app, it's an acceptable trade-off for a snappy UI.

Cost vs. Accuracy
Running LLMs for every single interaction is financially unsustainable. By implementing the Tiered AI workflow, we trade a small amount of "reasoning depth" for a massive reduction in token costs and a significant boost in response speed.

Complexity vs. Reliability
Moving from a monolith to microservices introduces the "Distributed Systems Tax." You must now manage network partitions, circuit breakers, and distributed tracing (Jaeger/Zipkin). We implement graceful degradation: if the leaderboard service is unreachable, we simply hide the leaderboard UI rather than crashing the entire app.

Scaling the Content Pipeline

Creating lessons manually is the ultimate bottleneck. To scale, the architecture must treat content as code.

We use a CMS-to-API pipeline. Content creators define lessons in a structured format (JSON/YAML), which is then validated by an automated suite of tests to ensure there are no broken links or impossible grammar puzzles. This content is then pushed to a CDN (Content Delivery Network) so that lesson data is served from the edge, closest to the user, reducing the load on origin servers.

flowchart

Final Engineering Takeaways

If you are building a high-scale, interactive application, keep these core principles in mind:

  • Decouple the Write Path: Never let a heavy database write block the UI. Use a message bus (Kafka/RabbitMQ) to handle state updates asynchronously.
  • Smarter Caching: Don't just cache everything. Use specialized structures like Redis Sorted Sets for rankings and tiered AI models to balance cost and latency.
  • Right Tool for the Job: Use Distributed SQL for critical data (money, streaks) and NoSQL or Columnar stores for fast-access or analytical data (profiles, logs).
  • Design for Failure: Assume your services will fail. Implement circuit breakers and graceful degradation so a failure in a non-critical service doesn't kill the core user experience.

Scaling to 100 million users isn't about finding the perfect piece of software; it's about expertly managing the trade-offs between speed, cost, and correctness.

Top comments (0)