•CASE STUDY

Text-to-Video Generation Service (Sora)

7 min read·1,289 words·Advanced

Asked at

8 candidate reports between Jun 2026 and Aug 2026

How to use this case study

SDE-2 / Mid

  • Explain the async job flow
  • From submit and queue to a GPU worker
  • Video storage and status polling
  • How the user downloads the result

SDE-3 / Senior

  • Go deeper on the GPU scheduler (priorities by tier, fairness)
  • Leases and heartbeats for long jobs
  • Retries without duplicate expensive work
  • Cancellation

Staff / Principal

  • Discuss capacity planning for scarce GPUs
  • Multi-stage pipelines with checkpoints
  • Cost controls and quotas
  • Preemption
  • Multi-region GPU pools

Problem RestatementProblem

Design a service like OpenAI's Sora. A user types a prompt ("a corgi surfing at sunset"), and the system generates a video. Generation takes minutes on expensive GPUs, and GPUs are scarce and sometimes fail. So the request can't be synchronous. It is an asynchronous job: the user submits, sees progress, can cancel, and downloads the video when it's ready.

Paid tiers get higher priority and larger quotas. The interviewer usually cares less about the ML model and more about jobs, scheduling and reliability around scarce compute.

RequirementsRequirements

1.1 Functional

  • Submit a prompt with options (length, resolution) → get a job_id.
  • See status and progress: queued (with position), running (percent), done or failed.
  • Cancel a job.
  • Download or stream the finished video, and see history.
  • Quotas and priority per tier (free, plus, pro).

1.2 Non-Functional

  • No lost jobs: a submitted job eventually completes or fails clearly.
  • Efficient GPU use: GPUs should rarely sit idle, and work isn't done twice.
  • Fairness: free users still make progress, and paid users wait less.
  • Cost control: stop runaway usage and abuse.

1.3 Scale Estimates

  • 1M videos/day ≈ 12 jobs/sec on average, with peaks of 50/sec.
  • Each video needs ~2 GPU-minutes on average → 2M GPU-minutes/day ≈ 1,400 GPUs busy on average, more at peak. That's why queues form.
  • Output: ~20 MB per video → 20 TB/day to object storage, served via CDN.

1.4 API Design

  • POST /v1/videos (Idempotency-Key) { prompt, duration_s: 10, resolution: "1080p" } → { job_id, status: "queued" }
  • GET /v1/videos/{job_id} → { status, progress, queue_position, video_url? }
  • POST /v1/videos/{job_id}/cancel
  • Optional webhook or SSE for progress updates.

High-Level ArchitectureArchitecture

2.1 Overview

  • Job API: validates, checks quota and moderation, creates the job and returns immediately.
  • Job DB: the durable job state (source of truth).
  • GPU Scheduler: keeps priority queues per tier and assigns jobs to free GPU workers.
  • GPU workers: run the model. They send heartbeats and progress, and save checkpoints.
  • Post-processing workers (CPU): encode, make thumbnails, add a watermark.
  • Object storage + CDN: final videos and intermediate checkpoints.
  • Notification: tells the user when the job is done.

2.2 Architecture Diagram

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    U["User"] --> API["Job API - quota, moderation"]
    API --> DB[("Job DB")]
    API --> SCH["GPU Scheduler - tier queues"]
    SCH -->|"lease job"| G1["GPU worker"]
    SCH -->|"lease job"| G2["GPU worker"]
    G1 -->|"heartbeat, progress"| SCH
    G1 --> CK[("Checkpoints - object storage")]
    G1 --> PP["Post-process: encode, thumbnail"]
    PP --> OS[("Videos - object storage")]
    OS --> CDN["CDN"]
    SCH --> DB
    PP --> N["Notify user"]
    U -->|"poll status / download"| API

Data ModelData model

jobs:
  job_id, user_id, tier, prompt, params, status (queued, running, postprocessing, succeeded,
  failed, cancelled), priority, attempt, worker_id, lease_until, progress, checkpoint_key,
  output_key, gpu_seconds_used, created_at, started_at, finished_at
usage:
  user_id, period, gpu_seconds_used, videos_count      -- for quotas

Key FlowsFlows

4.1 Submit

  1. Check the prompt against content policy, and check the user's quota (e.g., 50 videos/month) and number of concurrent jobs.
  2. Create the job as queued (the idempotency key stops double submits) and add it to the scheduler queue for the user's tier.
  3. Return job_id. The client polls every few seconds or listens on SSE.

4.2 Run

  1. When a GPU worker is free, the scheduler picks the next job (see fairness below) and leases it to the worker: lease_until = now + 60s.
  2. The worker heartbeats every ~15 seconds with progress, extending the lease.
  3. It saves checkpoints every few minutes (e.g., finished segments), so a crash doesn't lose all the work.
  4. When done, it uploads raw output, and post-processing encodes it. The job becomes succeeded and the user is notified.

4.3 Cancel

Mark cancelled. The worker sees it in the next heartbeat response, stops and frees the GPU.

Deep Dive A — Sharing GPUs that everyone wantsDeep dive

A job takes minutes on a GPU that costs dollars an hour, and there are never enough. How the queue is arranged is the product for everybody who is waiting.

Weak

One queue, first come first served

Every job goes into one FIFO. Fair in the plainest sense, and easy to reason about.

Paying customers wait behind free-tier jobs, which is the wrong business answer, and one user submitting fifty jobs occupies the fleet for an hour. FIFO also has no way to express that a 4-second 480p clip and a 60-second 1080p render are different work — they queue identically and the short job waits behind the long one.

Good

A queue per tier, strict priority

Pro, plus and free each get a queue, and the scheduler always drains the highest tier first. Paying users now get their GPUs.

Strict priority starves the bottom. Whenever pro demand covers the fleet — which is most of the working day — free jobs never run at all, not slowly. The free tier stops being slow and becomes broken, and it is the tier that produces the paying users.

Best

Weighted fair sharing, with a per-user ceiling

Give each tier a share of finishing slots instead of absolute precedence: of every 10 slots that free up, 6 to pro, 3 to plus, 1 to free.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  Q1["Pro queue"] --> W["Weighted scheduler - 6 / 3 / 1 of free slots"]
  Q2["Plus queue"] --> W
  Q3["Free queue"] --> W
  W --> CAP{"User already running 2 jobs?"}
  CAP -->|"yes"| HOLD["Hold - next user's turn"]
  CAP -->|"no"| POOL["Place by job size"]
  POOL --> SMALL["Short / 480p pool"]
  POOL --> BIG["Long / 1080p - bigger or multi-GPU"]

Three things fall out of this that the earlier rungs could not express:

  • Nobody starves. The free tier is slower, which is the intended product difference, and it always moves.
  • A per-user cap of two running jobs stops one account from spending a tier's whole share.
  • Right-sized placement. Long and high-resolution jobs go to a pool with bigger GPUs, so they stop blocking short ones.

Then show a queue position and ETA from queue depth and average job time. It costs almost nothing and removes the behaviour that hurts most: users cancelling and resubmitting because nothing appears to be happening, which throws away the GPU minutes already spent.

Deep Dive B — Failures without duplicate expensive workDeep dive

  • Lease expiry: if heartbeats stop (the worker crashed), the scheduler re-queues the job with attempt + 1, starting from the last checkpoint rather than from zero.
  • Fencing: a "zombie" worker from attempt 1 that wakes up late can't overwrite the result, because writes include the attempt number, and only the current attempt is accepted.
  • Retry limits: after 3 failed attempts, mark failed, refund the user's quota, and alert if many jobs fail (a bad model version or bad nodes).
  • Idempotent outputs: results are written to videos/{job_id}/attempt-{n}.mp4, and the job record points to the winner.
  • Cost controls: hard caps on duration and resolution per tier, per-user daily GPU-second budgets, and alerts on unusual spending.

Trade-offs & AlternativesTrade-offs

DecisionChoiceWhyAlternative
Request styleAsync job + polling/SSEMinutes-long workSynchronous: timeouts, wasted GPU on disconnect
SchedulingWeighted fair queues per tierPaid priority without starving freeStrict priority: free tier may never run
ReliabilityLeases + heartbeats + checkpointsRecover without redoing everythingRestart from scratch: wastes GPU time
Status updatesPolling + optional SSESimple, robustWebSocket only: more connection management

Common Follow-up QuestionsFollow-ups

  • "How do you plan capacity?" Track queue wait time per tier. When pro waits exceed the target, add GPUs (slow, since reserved capacity is bought ahead) or temporarily lower free-tier limits.
  • "Multi-stage pipelines?" Model stages (draft → upscale → audio) as separate tasks in a small DAG, each with its own queue and checkpoint.
  • "Abuse?" Moderate prompts before queueing and outputs before release, and rate-limit new accounts.

Wrap-UpWrap-up

Make video generation an asynchronous job: validate and store it, queue it by tier, and let a GPU scheduler lease jobs to workers with heartbeats and checkpoints. Use weighted fair queues and per-user limits to share scarce GPUs, fencing and attempt numbers to stop duplicate work, and quotas and cost caps to control spending. Store results in object storage behind a CDN and report progress through polling or SSE.

More Case Studies

Frequently Asked Questions

What is the Text-to-Video Generation Service (Sora) system design question?

Text-to-Video Generation Service (Sora) is a system design interview question asked at FAANG companies. It covers ai / ml, scheduling, distributed systems, storage and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Text-to-Video Generation Service (Sora) question?

Google, OpenAI have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Text-to-Video Generation Service (Sora) interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Text-to-Video Generation Service (Sora) question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →