> [Agent navigation index](/llms.txt)

# Handle synchronous typed results

Model expected synchronous failures with tagged errors, narrow or match each branch, and transform or chain results without throwing.

## Decide between a result and an exception

Return `Result<T, E>` for expected domain, validation, network, or provider outcomes. Throw for programmer errors, impossible states, and broken invariants. This keeps recoverable branches visible in types without converting every defect into normal control flow.

Import through Brix rather than installing or importing `better-result` directly:

```ts
import { Result, TaggedError } from "brix/result";
import type { Err, Ok, ResultPromise } from "brix/result";

```

The public entry point exports `Result`, `TaggedError`, `resultPromise`, and the `ResultPromise`, `Ok`, and `Err` types.

## Model a tagged expected error

Tagged errors provide a stable discriminant and typed payload:

```ts
import { Result, TaggedError } from "brix/result";

class BookingNotFoundError extends TaggedError("BookingNotFoundError")<{
  bookingId: string;
}>() {}

type Booking = { id: string; reference: string };
const bookings = new Map<string, Booking>();

function findBooking(id: string): Result<Booking, BookingNotFoundError> {
  const booking = bookings.get(id);
  return booking ? Result.ok(booking) : Result.err(new BookingNotFoundError({ bookingId: id }));
}

```

Use distinct tags for failures callers treat differently. Include useful context, but do not put secrets into error payloads that may be logged or serialized.

## Narrow or match a result

`isErr()` and `isOk()` narrow the corresponding branch:

```ts
const result = findBooking(bookingId);

if (result.isErr()) {
  return { status: 404, bookingId: result.error.bookingId };
}

return result.value;

```

Use `match` when both branches produce one output:

```ts
const label = result.match({
  ok: (booking) => booking.reference,
  err: () => "Missing booking",
});

```

Reserve `unwrap()` for tests or boundaries where failure should terminate the operation. `unwrapOr(fallback)` is appropriate for a genuine default, not for hiding a failure that needs user feedback or observability.

## Transform and chain synchronously

`map` changes the success value and `mapError` changes the failure:

```ts
const summary = findBooking(bookingId)
  .map((booking) => ({ id: booking.id, reference: booking.reference }))
  .mapError((error) => ({ source: "booking", error }));

```

Use `andThen` when the next synchronous operation also returns a `Result`:

```ts
const confirmed = findBooking(bookingId).andThen((booking) =>
  booking.reference
    ? Result.ok({ booking, confirmed: true })
    : Result.err(new InvalidBookingError({ bookingId: booking.id })),
);

```

Use `tap`, `tapError`, or `tapBoth` for observation that must not alter either branch.

Continue with [compose asynchronous typed results](/docs/results-async).
