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
- Read the types and predict the output before running the examples.
- 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.
Handle the error variant before accessing the session. The parser already validates the input and trims the topic.
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.