Result types are a functional programming pattern that makes error handling explicit and type-safe. Instead of throwing exceptions or returning null/undefined, functions return a Result type that can represent either success (with a value) or failure (with an error).
The key insight is that errors become part of your type system. When a function returns Result<User, DatabaseError>, you immediately know it might fail with a database error, and the compiler forces you to handle both cases.
This approach has several advantages over traditional exception handling:
Explicit Error Handling: You can't accidentally ignore errors because the type system requires you to handle both success and failure cases.
Composability: Result types work beautifully with functional programming patterns. You can chain operations that might fail without nested try-catch blocks.
Type Safety: Both your success values and error types are fully typed, giving you better autocomplete and compile-time guarantees.
Result types typically provide methods for checking the state (isOk(), isErr()), extracting values (unwrap()), and transforming success values while preserving errors (map(), flatMap()).
The pattern originated in functional languages like Haskell (Either type) and was popularized by Rust's Result enum. It's now being adopted in many languages as developers recognize the benefits of explicit error handling.
1// Traditional approach with exceptions
2function divideUnsafe(a: number, b: number): number {
3 if (b === 0) {
4 throw new Error('Division by zero');
5 }
6 return a / b;
7}
8
9// Result-based approach
10function divideSafe(a: number, b: number): Result<number, string> {
11 if (b === 0) {
12 return Result.err('Division by zero');
13 }
14 return Result.ok(a / b);
15}
16
17// Usage - errors must be handled explicitly
18const result = divideSafe(10, 2);
19if (result.isOk()) {
20 console.log('Result:', result.unwrap());
21} else {
22 console.log('Error:', result.unwrap()); // This would throw!
23}