The smallest possible version, no library required.
type Result<A, E> = { ok: true; value: A } | { ok: false; error: E }
function parseAge(input: string): Result<number, string> {
const n = parseInt(input)
if (isNaN(n)) return { ok: false, error: "Not a number" }
if (n < 0) return { ok: false, error: "Negative age" }
return { ok: true, value: n }
const result = parseAge("25")
The discriminant ok lets TypeScript narrow inside each branch. The value channel is only reachable after the success check; the error channel is only reachable after the failure check.
No casts, no exceptions.
When to Use
Local code and toy examples where you do not need composition, dependency injection, retry, or async. Once any of those enter the picture, reach for Effect.
Your turn: check the ok discriminant first, then read value or error from inside the narrowed branch.