Skip to content

Starter code bank

Each starter is an incomplete, self-contained exercise. Work in the interview-labs/ project. Read the lesson first, then try to complete the missing pieces before opening the Hints.

Lesson 1 — App anatomy and JSX

import { createRoot } from 'react-dom/client';

function App() {
  // TODO: add header, main, footer, and Dashboard
  return null;
}

function Dashboard() {
  return <section aria-labelledby="dashboard-title">{/* TODO */}</section>;
}

const root = createRoot(document.getElementById('root')!);
root.render(<App />);

Lesson 2 — Rendering, lists, and conditional UI

type Topic = { id: string; title: string; completed: boolean };

function TopicList({ topics }: { topics: Topic[] }) {
  const [search, setSearch] = useState('');

  // TODO: derive filtered topics and render a useful empty state
  const filtered = topics;

  return (
    <>
      <input
        type="search"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
      <ul>
        {filtered.map((topic, index) => (
          // TODO: use a stable key
          <li key={index}>{topic.title}</li>
        ))}
      </ul>
    </>
  );
}

Lesson 3 — Props, composition, and component contracts

function ProgressRing(props: /* TODO: define a narrow contract */) {
  // TODO: clamp the value and render an accessible progress ring
  return null;
}

function Dashboard() {
  return (
    <div>
      <ProgressRing value={105} label="Overall readiness" />
    </div>
  );
}

Lesson 4 — Hooks, reducer state, and derived data

type Topic = { id: string; title: string; completed: boolean; minutes: number };

type State = { topics: Topic[] };

type Action =
  | { type: 'add'; topic: Topic }
  | { type: 'toggle'; id: string };

function reducer(state: State, action: Action): State {
  // TODO: implement pure, immutable transitions
  return state;
}

function Tracker() {
  const [state, dispatch] = useReducer(reducer, { topics: [] });

  // TODO: derive completed count and total minutes during render
  return null;
}

Lesson 5 — Context and dependency boundaries

const TrackerContext = createContext<{
  state: State;
  dispatch: Dispatch<Action>;
} | null>(null);

export function TrackerProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(reducer, { topics: [] });
  // TODO: provide the value and memoize when it helps
  return null;
}

export function useTracker() {
  // TODO: guard against missing provider
  return useContext(TrackerContext);
}

Lesson 6 — React Router and lazy routes

import { createHashRouter, RouterProvider } from 'react-router-dom';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const TopicDetail = lazy(() => import('./pages/TopicDetail'));

const router = createHashRouter([
  {
    path: '/',
    // TODO: add a layout and nested routes
    children: [],
  },
]);

function App() {
  return (
    <Suspense fallback={<p>Loading</p>}>
      <RouterProvider router={router} />
    </Suspense>
  );
}

Lesson 7 — Typed controlled forms and validation

type Session = { topic: string; minutes: number; notes: string };

function SessionForm({ onSubmit }: { onSubmit: (s: Session) => void }) {
  const [topic, setTopic] = useState('');
  const [minutes, setMinutes] = useState('');
  const [notes, setNotes] = useState('');
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    // TODO: validate minutes between 1 and 240, then call onSubmit
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="topic">Topic</label>
      <input id="topic" value={topic} onChange={(e) => setTopic(e.target.value)} />
      {/* TODO: minutes and notes fields, plus error announcement */}
      <button type="submit">Save session</button>
    </form>
  );
}

Lesson 8 — Fetching and async state

type Async<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function useTopics() {
  const [state, setState] = useState<Async<Topic[]>>({ status: 'idle' });

  useEffect(() => {
    const controller = new AbortController();
    // TODO: fetch, check ok, validate, and handle race/stale cancellation
    return () => controller.abort();
  }, []);

  return state;
}

Lesson 9 — Effects, cancellation, and event reasoning

function useTopicDetails(topicId: string) {
  const [details, setDetails] = useState<Topic | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    // TODO: fetch details and guard against stale results

    return () => controller.abort();
  }, [/* TODO: what belongs in the dependency list? */]);

  return details;
}

Lesson 10 — Vitest and React Testing Library

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

it('toggles a topic', async () => {
  render(<TopicList topics={[{ id: '1', title: 'React', completed: false }]} />);

  const checkbox = screen./* TODO: choose the right query */('checkbox');
  await userEvent.click(checkbox);

  expect(checkbox).toBeChecked();
});

Lesson 11 — Rendering performance and profiling

import { useMemo } from 'react';

function TopicSummary({ topics }: { topics: Topic[] }) {
  // TODO: decide if useMemo is justified here
  const expensive = topics
    .filter((t) => !t.completed)
    .sort((a, b) => b.priority - a.priority);

  return (
    <ul>
      {expensive.map((t) => (
        <li key={t.id}>{t.title}</li>
      ))}
    </ul>
  );
}

Lesson 12 — Architecture and production boundaries

Document the architecture in ARCHITECTURE.md:

# Architecture record

## Boundaries
- TODO: list route, domain state, server state, and transport boundaries.

## Rendering strategy
- SPA / SSR / streaming: TODO with tradeoffs.

## Failure modes
- TODO: list error recovery, a11y paths, and security trust boundaries.

Lesson 13 — Capstone change request

type ShareablePlan = {
  version: number;
  topicIds: string[];
  targetDate: string;
  weeklyMinutes: number;
};

function encodePlan(plan: ShareablePlan): string {
  // TODO: serialize and encode the plan for the URL
  return '';
}

function decodePlan(encoded: string): ShareablePlan {
  // TODO: validate version and throw on malformed data
  throw new Error('Not implemented');
}