Syncing account progress…

Back

Intermediate · 20 min

Project: a typed practice planner

Combine runtime validation and discriminated results in a small practice planner.

Build a trusted session

Separate input validation from formatting. The parser accepts unknown data and returns either a validated session or an explicit error. Building a new object selects the fields the planner uses; trimming the topic is a deliberate runtime transformation.

type Session = { topic: string; minutes: number };
type Result = { kind: "ok"; session: Session } | { kind: "error"; message: string };
function parseSession(input: unknown): Result {
  if (typeof input === "object" && input !== null
      && "topic" in input && typeof input.topic === "string" && input.topic.trim().length > 0
      && "minutes" in input && typeof input.minutes === "number"
      && isFinite(input.minutes) && input.minutes > 0 && Math.floor(input.minutes) === input.minutes) {
    return { kind: "ok", session: { topic: input.topic.trim(), minutes: input.minutes } };
  }
  return { kind: "error", message: "Invalid session" };
}
const result = parseSession({ topic: " Travel ", minutes: 15 });
console.log(result.kind === "ok" ? result.session.topic : result.message);

Output

Travel

Summarize typed sessions

Once records have been checked, other functions can accept the smaller Session contract. Keep calculations separate from display text. Here a typed array supports a total while the empty array naturally produces zero.

type Session = { topic: string; minutes: number };
function totalMinutes(sessions: readonly Session[]): number {
  return sessions.reduce((total, session) => total + session.minutes, 0);
}
const sessions: Session[] = [{ topic: "Travel", minutes: 15 }, { topic: "Music", minutes: 20 }];
console.log(totalMinutes(sessions));
console.log(totalMinutes([]));

Output

35
0

Put it into practice

  1. Read the types and predict the output before running the examples.
  2. Fix the task without using "any" or a type assertion, then check both practice questions.

Try it yourself

Code runs on this device. When you are signed in, drafts sync to your account.

Finish the practice planner

Fix "describe" to format a successful session as its topic followed by its minutes, or return the error message. Keep the parser and both inputs unchanged.

Show solution
type Session = { topic: string; minutes: number };
type Result = { kind: "ok"; session: Session } | { kind: "error"; message: string };
function parseSession(input: unknown): Result {
  if (typeof input === "object" && input !== null
      && "topic" in input && typeof input.topic === "string" && input.topic.trim().length > 0
      && "minutes" in input && typeof input.minutes === "number"
      && isFinite(input.minutes) && input.minutes > 0 && Math.floor(input.minutes) === input.minutes) {
    return { kind: "ok", session: { topic: input.topic.trim(), minutes: input.minutes } };
  }
  return { kind: "error", message: "Invalid session" };
}
function describe(result: Result): string {
  if (result.kind === "error") return result.message;
  return result.session.topic + ": " + result.session.minutes;
}
const summary = describe(parseSession({ topic: " Travel ", minutes: 15 }));
const rejected = describe(parseSession({ topic: "Art", minutes: -2 }));
console.log(summary);
console.log(rejected);

The parser establishes the runtime contract. The formatter narrows its result before reading session data, while invalid input keeps an explicit error path. Neither an assertion nor any is needed.

Practice