Effect-TS vs fp-ts vs neverthrow (2026)
TypeScript's try/catch loses type information about errors. These three libraries bring type-safe error handling to TypeScript.
Quick Comparison
| Feature | Effect-TS | fp-ts | neverthrow |
|---|---|---|---|
| Approach | Full framework | FP library | Result type only |
| Learning Curve | Steep | Steep | Easy |
| Bundle Size | Large | Medium | Tiny (~2KB) |
| Error Types | ✅ Typed | ✅ Typed | ✅ Typed |
| Async | ✅ Built-in | ⚠️ TaskEither | ✅ ResultAsync |
| Concurrency | ✅ Fibers | ❌ | ❌ |
| Dependencies | ✅ Services | ❌ | ❌ |
| Ecosystem | Growing fast | Mature | Minimal |
neverthrow — The Simple Choice
import { ok, err, Result } from 'neverthrow'
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return err('Division by zero')
return ok(a / b)
}
const result = divide(10, 2)
result.match(
(value) => console.log(`Result: ${value}`),
(error) => console.log(`Error: ${error}`),
)
// Or chain operations
divide(10, 2)
.map(n => n * 2)
.mapErr(e => `Failed: ${e}`)
Pros: Tiny, easy to learn, drop into any project. Cons: Just a Result type — no async handling, no dependency injection.
fp-ts — The FP Library
import { pipe } from 'fp-ts/function'
import * as E from 'fp-ts/Either'
import * as TE from 'fp-ts/TaskEither'
const divide = (a: number, b: number): E.Either<string, number> =>
b === 0 ? E.left('Division by zero') : E.right(a / b)
pipe(
divide(10, 2),
E.map(n => n * 2),
E.fold(
(error) => console.log(`Error: ${error}`),
(value) => console.log(`Result: ${value}`),
),
)
Pros: Comprehensive FP toolkit, mature, well-documented.
Cons: Verbose, steep learning curve, pipe everywhere, losing momentum to Effect.
Effect-TS — The Full Framework
import { Effect, pipe } from 'effect'
const divide = (a: number, b: number) =>
b === 0
? Effect.fail(new Error('Division by zero'))
: Effect.succeed(a / b)
const program = pipe(
divide(10, 2),
Effect.map(n => n * 2),
Effect.tap(n => Effect.log(`Result: ${n}`)),
)
Effect.runPromise(program)
Pros: Type-safe errors, concurrency, dependency injection, retries, scheduling — it's a full framework for building reliable software. Cons: Massive learning curve, large bundle, opinionated, changes how you write TypeScript.
Decision Guide
| Situation | Choose |
|---|---|
| Add Result type to existing project | neverthrow |
| Learning functional programming | fp-ts |
| Building a complex, reliable system | Effect-TS |
| Small team, quick project | neverthrow |
| Want typed errors only | neverthrow |
| Want everything (DI, concurrency, retries) | Effect-TS |
FAQ
Is Effect-TS worth the complexity?
For large applications with complex error handling, dependency injection, and concurrency needs — yes. For simple CRUD apps — overkill.
Is fp-ts dead?
Not dead, but losing momentum to Effect-TS. Effect provides a more pragmatic approach to the same problems.
Can I use neverthrow with React?
Yes. It's just a data structure. Use in server actions, API routes, or any TypeScript code.
Bottom Line
neverthrow for simple typed errors (80% of projects need only this). Effect-TS for complex systems requiring full reliability guarantees. fp-ts if you want classic Haskell-style FP. Start with neverthrow — upgrade to Effect when complexity demands it.