esteban@devtrillo:~/blog/writing$
← cd -
$ cat learning-effect-3-flatmap-do-this-then-that.md

Part 3: flatMap: Do This, Then That

Jul 12, 2026·1 min read
$ tail -f learning-effect-3-flatmap-do-this-then-that/readers
connecting…

A useful program rarely does one thing.

It parses an id. Then it fetches a user. Then it checks a permission. Then it writes something.

That is where flatMap shows up.

flatMap means:

If this Effect succeeds, use its value to choose the next Effect.

A small chain

const parseUserId = (input: string) =>
Number.isInteger(Number(input))
? Effect.succeed(Number(input))
: Effect.fail(new InvalidUserId(input));
const fetchUser = (id: number) =>
id === 1
? Effect.succeed({ id, name: "Ada" })
: Effect.fail(new UserNotFound(id));
const program = parseUserId("1").pipe(Effect.flatMap((id) => fetchUser(id)));

The second step needs the value from the first step.

That is why this is flatMap.

Why does this use flatMap?

Failure skips the rest

If parseUserId("abc") fails, fetchUser does not run.

That is the part I like.

The happy path reads straight down. The failure path stays in the type.

Effect.Effect<User, InvalidUserId | UserNotFound, never>;

The program can fail in either step.

If parseUserId fails, what happens to fetchUser?

The part that finally clicked

This:

parseUserId("1").pipe(Effect.flatMap(fetchUser));

is the Effect version of:

const id = parseUserId("1");
const user = fetchUser(id);

Except the failures are not hiding.

They are part of the result.

NOTE

My current rule: if the next step returns an Effect, reach for flatMap.