TypeScript Generic Types and Interfaces: Defaults and a Typed Result Type
November 6, 2026
Welcome to episode 22 of TypeScript from Zero, a series for complete beginners (no JavaScript needed). Welcome back to TypeScript from zero! Last time a function got a type parameter. Today the same idea for types: a type with a hole in it, that we fill in when we use it. We are in the shop project, with a new file called result.ts. Every code block below is the exact code and output from the video, ready to copy.
- All episodes are in the TypeScript from Zero playlist.
- Versions used: Node.js 26.13.0, npm 11.20.0, TypeScript 7.0.2.
Setup
We start in the terminal and open the project in VS Code:
cd shop
touch src/result.ts
code .1. Generic type
Box is an object type with one property, value. The type of that value is T, a placeholder. When we use Box, we fill the hole in angle brackets. Box of number holds a number. Box of string holds a string.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());$ node src/result.ts
6 HI2. Check
The filled type is checked. A Box of number with the text five inside is wrong. Node does not care, but tsc says: type string is not assignable to type number. TS2322.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());
const bad: Box<number> = { value: "five" };$ npx tsc --noEmit
src/result.ts:5:28 - error TS2322: Type 'string' is not assignable to type 'number'.
5 const bad: Box<number> = { value: "five" };
~~~~~
src/result.ts:1:17 - The expected type comes from property 'value' which is declared here on type 'Box<number>'
1 type Box<T> = { value: T };
~~~~~
Found 1 error in src/result.ts:53. Fix
We delete that line, and tsc is quiet again.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());$ npx tsc --noEmit4. Default
A type parameter can have a default, after an equals sign. Page of T has a list of items. If we write just Page, with no angle brackets, T is a string. So the page holds a list of words.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());
type Page<T = string> = { items: T[]; total: number };
const page: Page = { items: ["a", "b"], total: 2 };
console.log(page.items.length, page.total);$ node src/result.ts
6 HI
2 25. Result
Now a very useful generic. A function can succeed or fail, and the type should say so. Result has two shapes. One has ok true and a value. The other has ok false and an error. T is the type of the value, and E is the type of the error, with string as the default. Our divide function never throws. It returns a Result.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());
type Page<T = string> = { items: T[]; total: number };
const page: Page = { items: ["a", "b"], total: 2 };
console.log(page.items.length, page.total);
type Result<T, E = string> =
| { ok: true; value: T }
| { ok: false; error: E };
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return { ok: false, error: "cannot divide by zero" };
}
return { ok: true, value: a / b };
}$ npx tsc --noEmit6. Use
To use a result, we check ok first. Inside the if, tsc knows we have the value, and in the else, we have the error. Ten divided by two is five, and one divided by zero is a failed result, not a crash.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());
type Page<T = string> = { items: T[]; total: number };
const page: Page = { items: ["a", "b"], total: 2 };
console.log(page.items.length, page.total);
type Result<T, E = string> =
| { ok: true; value: T }
| { ok: false; error: E };
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return { ok: false, error: "cannot divide by zero" };
}
return { ok: true, value: a / b };
}
const r = divide(10, 2);
if (r.ok) {
console.log("Result:", r.value);
} else {
console.log("Error:", r.error);
}
console.log(divide(1, 0));$ node src/result.ts
6 HI
2 2
Result: 5
{ ok: false, error: 'cannot divide by zero' }7. Error
What if we read the value without checking ok? The value only exists in one of the two shapes. tsc stops us: property value does not exist on the failed shape. TS2339.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());
type Page<T = string> = { items: T[]; total: number };
const page: Page = { items: ["a", "b"], total: 2 };
console.log(page.items.length, page.total);
type Result<T, E = string> =
| { ok: true; value: T }
| { ok: false; error: E };
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return { ok: false, error: "cannot divide by zero" };
}
return { ok: true, value: a / b };
}
const r = divide(10, 2);
if (r.ok) {
console.log("Result:", r.value);
} else {
console.log("Error:", r.error);
}
console.log(divide(1, 0));
console.log(r.value);$ npx tsc --noEmit
src/result.ts:26:15 - error TS2339: Property 'value' does not exist on type 'Result<number, string>'.
Property 'value' does not exist on type '{ ok: false; error: string; }'.
26 console.log(r.value);
~~~~~
Found 1 error in src/result.ts:268. Fix
We delete that line. The check with ok is the only way to reach the value, and that is exactly the point.
src/result.ts:
type Box<T> = { value: T };
const num: Box<number> = { value: 5 };
const word: Box<string> = { value: "hi" };
console.log(num.value + 1, word.value.toUpperCase());
type Page<T = string> = { items: T[]; total: number };
const page: Page = { items: ["a", "b"], total: 2 };
console.log(page.items.length, page.total);
type Result<T, E = string> =
| { ok: true; value: T }
| { ok: false; error: E };
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return { ok: false, error: "cannot divide by zero" };
}
return { ok: true, value: a / b };
}
const r = divide(10, 2);
if (r.ok) {
console.log("Result:", r.value);
} else {
console.log("Error:", r.error);
}
console.log(divide(1, 0));$ npx tsc --noEmitRecap
Today: generic types with a type parameter in angle brackets, a default type, and a Result type that can succeed or fail, and that we must check. We will use it again in the project. Next episode: classes.