โ† Back to question bank
System DesignSeniorMedium#5011 ยท 50m

Design a video streaming surface

Lead a frontend system design interview for a video streaming surface. Cover requirements, state ownership, data fetching, rendering, accessibility, performance, testing, and rollout.

Answer Strategy

Treat a video streaming surface as a product surface with user workflows, failure modes, and operational constraints. Start by clarifying who uses it, what must be fast, what must be shareable, what permissions apply, and what data can be stale.

A strong senior answer separates five owners before naming components: URL state for shareable intent, server state for canonical data, local interaction state for in-progress UI, design-system primitives for reusable behavior, and telemetry for production proof.

Then turn the whiteboard into implementation contracts. The code below is a compact architecture registry: it forces you to name routes, state owners, loading/error states, accessibility obligations, performance budgets, and rollout checks.

Reference Implementation: Design a video streaming surface Architecture Contract

Use this as the implementation anchor during a frontend system design interview. It is deliberately framework-light so you can adapt it to React, Next.js, Remix, or a component platform.

type StateOwner = 'url' | 'server' | 'client' | 'design-system' | 'telemetry';
type RiskLevel = 'low' | 'medium' | 'high';

type FrontendBoundary = {
  owner: StateOwner;
  owns: string;
  examples: string[];
  failureMode: string;
};

type SystemDesignPlan = {
  surface: string;
  primaryWorkflow: string;
  boundaries: FrontendBoundary[];
  performanceBudget: {
    firstUsefulPaintMs: number;
    interactionResponseMs: number;
    maxInitialClientKb: number;
  };
  rollout: Array<{ risk: RiskLevel; check: string }>;
};

type RequestState<T> =
  | { tag: 'idle' }
  | { tag: 'loading'; previous?: T }
  | { tag: 'success'; data: T; receivedAt: number }
  | { tag: 'error'; message: string; previous?: T };

type ApiResult<T> = { ok: true; data: T } | { ok: false; message: string };

async function getJson<T>(
  url: string,
  signal?: AbortSignal
): Promise<ApiResult<T>> {
  try {
    const response = await fetch(url, { signal, headers: { accept: 'application/json' } });
    if (!response.ok) return { ok: false, message: 'HTTP ' + response.status };
    return { ok: true, data: (await response.json()) as T };
  } catch (error) {
    if (signal?.aborted) return { ok: false, message: 'Request cancelled' };
    return {
      ok: false,
      message: error instanceof Error ? error.message : 'Unknown error',
    };
  }
}

function useRemoteResource<T>(url: string) {
  const [state, setState] = React.useState<RequestState<T>>({ tag: 'idle' });

  React.useEffect(() => {
    const controller = new AbortController();
    setState((previous) => ({
      tag: 'loading',
      previous: previous.tag === 'success' ? previous.data : undefined,
    }));

    getJson<T>(url, controller.signal).then((result) => {
      if (controller.signal.aborted) return;
      if (result.ok) {
        setState({ tag: 'success', data: result.data, receivedAt: Date.now() });
      } else {
        setState((previous) => ({
          tag: 'error',
          message: result.message,
          previous: previous.tag === 'loading' ? previous.previous : undefined,
        }));
      }
    });

    return () => controller.abort();
  }, [url]);

  return state;
}

function createSystemDesignPlan(surface: string): SystemDesignPlan {
  return {
    surface,
    primaryWorkflow: 'User completes the highest-value task without losing context.',
    boundaries: [
      {
        owner: 'url',
        owns: 'shareable filters, selected entity, tab, and return path',
        examples: ['query string', 'route segment', 'hash for local-only anchors'],
        failureMode: 'refresh or shared links drop the user into a different state',
      },
      {
        owner: 'server',
        owns: 'canonical entities, permissions, pagination cursors, freshness',
        examples: ['typed API client', 'query cache', 'schema validation'],
        failureMode: 'UI renders data the user can no longer access or trust',
      },
      {
        owner: 'client',
        owns: 'drafts, focused item, optimistic edits, open panels',
        examples: ['component state', 'reducer', 'external store when cross-route'],
        failureMode: 'in-progress interaction is overwritten by background updates',
      },
      {
        owner: 'design-system',
        owns: 'keyboard behavior, accessible labels, density, empty states',
        examples: ['combobox', 'dialog', 'table', 'toast', 'skeleton'],
        failureMode: 'teams reimplement behavior inconsistently across screens',
      },
      {
        owner: 'telemetry',
        owns: 'latency, errors, abandonment, feature flag health',
        examples: ['web vitals', 'client errors', 'workflow checkpoints'],
        failureMode: 'rollout looks successful because nobody can see silent failure',
      },
    ],
    performanceBudget: {
      firstUsefulPaintMs: 1800,
      interactionResponseMs: 100,
      maxInitialClientKb: 180,
    },
    rollout: [
      { risk: 'high', check: 'guard with feature flag and kill switch' },
      { risk: 'medium', check: 'ship read-only path before mutation-heavy flows' },
      { risk: 'medium', check: 'alert on client error rate and abandoned workflow' },
      { risk: 'low', check: 'document ownership and follow-up extraction points' },
    ],
  };
}

Testing Strategy

Convert the answer into observable behavior. In a mid-senior interview, say which behaviors are covered by unit tests, interaction tests, accessibility checks, and one browser smoke path.

Requirements
Write acceptance criteria for the primary workflow, non-goals, permissions, device constraints, and freshness expectations.
Contracts
Add API adapter contract tests and reducer tests for URL, server, and local interaction state.
Experience
Cover loading, empty, partial-data, offline, keyboard, and screen-reader states in component stories or interaction tests.
Operations
Ship behind a flag, watch client error rate and workflow abandonment, and define rollback before launch.
test('frontend system design plan covers durable ownership boundaries', () => {
  const plan = createSystemDesignPlan('a video streaming surface');

  expect(plan.boundaries.map((boundary) => boundary.owner)).toEqual([
    'url',
    'server',
    'client',
    'design-system',
    'telemetry',
  ]);
  expect(plan.performanceBudget.interactionResponseMs).toBeLessThanOrEqual(100);
  expect(plan.rollout.some((item) => item.check.includes('feature flag'))).toBe(true);
});

Interviewer Signal

Shows whether you can turn a broad product surface into a durable frontend architecture with clear contracts.

Constraints

  • Spend the first five minutes on requirements and non-goals.
  • Name client, server, cache, and URL state separately.
  • Include accessibility, performance, and observability before the end.

Model Answer Shape

  • Clarify users, scale, latency, collaboration, offline, and device constraints.
  • Draw the route/component/data-flow shape before diving into component props.
  • Choose explicit boundaries for API clients, cache, local state, design-system primitives, and tests.

Tradeoffs

  • Generic primitives increase reuse but require stronger documentation and ownership.
  • Client-side richness improves speed after load but can raise hydration and bundle costs.
  • Real-time updates help freshness but complicate ordering, backpressure, and recovery.

Edge Cases

  • Slow network and partial data.
  • Permission changes while the user is on the page.
  • Large datasets, long sessions, and stale caches.

Testing And Proof

  • Contract tests for API adapters.
  • Interaction tests for critical workflows.
  • Performance budget and E2E scenario for the most important path.

Follow-Ups

  • How would you roll this out safely to 1% of users?
  • What would become a shared platform primitive after the second product adopted it?