Skip to content
Back to skills

Functional Design

ASecurity

Use when starting design or review, deciding how much design a task warrants, from a throwaway script to a subsystem, unsure which skill fits, or the requirement is undecided.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 21, 2026
developmentjavascripttypescriptgojavashellreactnextjsexpresstestingrefactoring

Security analysis

A100/100

Pro scans all 7 files and shows the line behind each finding

Scanned October 21, 2026

npx -y skills add yutna/functional-design-skills --skill functional-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Functional Design?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Functional Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yutna-functional-design/badge)](https://www.skillsdirectory.com/skills/yutna-functional-design)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: functional-design
description: Use when starting design or review, deciding how much design a task warrants, from a throwaway script to a subsystem, unsure which skill fits, or the requirement is undecided.
license: MIT
metadata:
  pack: functional-design-skills
  version: 3.3.0
---

# Functional Design

## Overview

This pack is a set of executable design rules for codebases written in a
functional style. It covers complexity and module boundaries, modelling a
domain in types, composing workflows, immutability, and where effects
belong.

One method runs through all of it: make the domain explicit in types,
express behaviour as composed total functions, push effects to the edges,
and hide everything else behind deep interfaces.

## When to use

- Starting a new module, workflow, endpoint, or bounded context
- A small change forces edits in many places, or nobody can predict what
  a change will break
- Reviewing a design or a diff and needing a concrete checklist
- Unsure which skill in this pack applies to the problem in front of you

Not for: language syntax lookups, build and tooling problems, or
deciding what the software should do in the first place.

## How much design does this need?

Decide this before opening a skill. The row is chosen by something you
can observe about the task, not by how important it feels.

| The task                      | Apply               | Skip         |
| ----------------------------- | ------------------- | ------------ |
| Output is an answer           | nothing             | everything   |
| One function, module exists   | names, totality     | modelling    |
| A feature in an existing flow | the flow's idiom    | new contexts |
| A new module or workflow      | the design loop     | nothing      |
| A new bounded context         | loop, then contexts | nothing      |

Reading the rows:

- **Output is an answer.** A script run once, a probe, a migration you
  will delete. Write it, get the answer, label it throwaway.
- **One function, module exists.** Match what the module already does.
  Give the function an honest signature and a precise name. Do not
  introduce a wrapper type, an error union, or a boundary the module
  does not already have.
- **A feature in an existing flow.** The idiom is already decided; your
  job is to fit it and never leave the code worse.
  [functional-programming-strategically](../functional-programming-strategically/SKILL.md)
  says what more a change owes. New types only where the feature
  genuinely introduces a new concept.
- **A new module or workflow.** The full loop below is worth its cost,
  because other code is about to depend on the shape you choose.
- **A new bounded context.** The loop, plus the language and boundary
  work in `functional-capturing-the-domain` and
  `functional-enforcing-consistency-boundaries`.

Applying a heavier row than the task calls for is itself a design
failure: it adds interface, indirection, and reading cost that the
problem did not ask for, which is the definition of complexity these
skills exist to remove. See
[calibration.md](references/calibration.md) for the failure modes in
both directions.

## How strictly a rule is meant

Every numbered item under a skill's `Core rules` opens with one of three
labels. The label says what it means to do otherwise.

- **Rule.** Doing otherwise is a defect, in any code that is kept.
- **Default.** Do this unless the condition the item states holds. An
  item that states none is one this pack knows no exception to: follow
  it, and where you still do otherwise, give the reason in the reply or
  the pull request.
- **Judgement.** Two answers are defensible. The item says what tips the
  choice, and where nothing tips it either answer is right.

Two things come before any label. The table above decides how much of
the pack a task takes on at all, so a rule in a skill that the row skips
binds nothing. And a convention of this codebase, written down or simply
followed throughout it, comes before all three labels, a `Rule`
included: keep to it, and where keeping to it costs correctness, say
so.

## Is the problem decided?

The last two rows assume the problem is settled and the open question is
what shape the code takes. Check that before entering the loop, in one
move:

**State one rule the software must follow, with an example it accepts
and an example it refuses.**

If that comes out in a sentence, write the sentence down and start. A
request that already states its rules has passed the check: write each
back in a sentence, with an example it accepts and one it refuses, and
start. The check costs thirty seconds, and what it asks for is a spoken
rule, not a document. Demanding a written
specification from someone who can simply say the rule is applying a
heavier row than the task calls for. Edge cases that come up later are
questions to ask as you go; they do not gate the start.

If it does not come out, the task is in no row on this table, and what
is missing is not design:

- the rules, each with an example it accepts and one it refuses
- the questions nobody can answer yet, each with the person who owns it

Producing those is a different activity, usually with different people
in the room, and no amount of modelling substitutes for it. Types
written over an undecided problem still compile; they encode the guess
and make it expensive to revisit.

Working alone, say in your reply that the task is in no row of the
table and that this work comes before any design, then what has to be
produced: the rules, each with an example it accepts and one it refuses,
and the questions, each with the role that can answer it, or the person
where a file names one. Write out only the rules someone has stated, in
the request or in a file: a guess written as an example, even one
labelled a hypothesis or an assumption, reads as a requirement the next
person inherits. Then stop before modelling.
[working-alone.md](references/working-alone.md) says what stands in for
the people a step assumes, and which of its steps this check overrides.

[functional-capturing-the-domain](../functional-capturing-the-domain/SKILL.md)
covers part of this. Its discovery procedure produces the events, the
vocabulary, and the places where the experts disagree, which is most of the way
there. It stops short of rules with worked examples, and that last piece is not
design work.

## The design loop

Run in order. Skip a step only when the previous one proved it irrelevant.

1. Say what the software must do in the domain's own words, as commands
   and events. See
   [functional-capturing-the-domain](../functional-capturing-the-domain/SKILL.md).
2. Model the data so impossible states cannot be built. See
   [functional-modeling-with-algebraic-types](../functional-modeling-with-algebraic-types/SKILL.md)
   and
   [functional-making-illegal-states-unrepresentable](../functional-making-illegal-states-unrepresentable/SKILL.md).
3. Write the workflow as a pipeline of total functions, typed end to end.
   See
   [functional-designing-workflow-pipelines](../functional-designing-workflow-pipelines/SKILL.md).
4. Decide where effects live: pure core, effectful shell. See
   [functional-separating-pure-core-from-shell](../functional-separating-pure-core-from-shell/SKILL.md).
5. Shape the modules so each hides more than it reveals. See
   [functional-designing-deep-modules](../functional-designing-deep-modules/SKILL.md)
   and
   [functional-hiding-information](../functional-hiding-information/SKILL.md).
6. Name things precisely and comment only what code cannot say. See
   [functional-choosing-precise-names](../functional-choosing-precise-names/SKILL.md).
7. Audit the result against the red-flag catalogue. See
   [functional-reviewing-functional-design](../functional-reviewing-functional-design/SKILL.md).

## Symptom to skill

- Small change touches many files -> [functional-diagnosing-complexity](../functional-diagnosing-complexity/SKILL.md)
- Shipping fast now, paying later -> [functional-programming-strategically](../functional-programming-strategically/SKILL.md)
- Reader cannot tell what matters -> [functional-deciding-what-matters](../functional-deciding-what-matters/SKILL.md)
- Interface as large as the code behind it -> [functional-designing-deep-modules](../functional-designing-deep-modules/SKILL.md)
- Callers know internal details -> [functional-hiding-information](../functional-hiding-information/SKILL.md)
- Layers repeat the same abstraction -> [functional-separating-layers](../functional-separating-layers/SKILL.md)
- Unsure whether to split a function -> [functional-splitting-and-joining-code](../functional-splitting-and-joining-code/SKILL.md)
- Dependency direction feels wrong -> [functional-applying-solid-functionally](../functional-applying-solid-functionally/SKILL.md)
- Code and business speak different words -> [functional-capturing-the-domain](../functional-capturing-the-domain/SKILL.md)
- Types are records of primitives -> [functional-modeling-with-algebraic-types](../functional-modeling-with-algebraic-types/SKILL.md)
- Shape comes from config or varies per tenant -> [functional-choosing-types-or-plain-data](../functional-choosing-types-or-plain-data/SKILL.md)
- Booleans and nullable fields encode state -> [functional-making-illegal-states-unrepresentable](../functional-making-illegal-states-unrepresentable/SKILL.md)
- Strings and numbers used raw -> [functional-constraining-primitive-values](../functional-constraining-primitive-values/SKILL.md)
- Status fields and if-ladders -> [functional-modeling-state-machines](../functional-modeling-state-machines/SKILL.md)
- Unclear what one transaction covers -> [functional-enforcing-consistency-boundaries](../functional-enforcing-consistency-boundaries/SKILL.md)
- Use case spread across services -> [functional-designing-workflow-pipelines](../functional-designing-workflow-pipelines/SKILL.md)
- Functions do not fit together -> [functional-composing-functions](../functional-composing-functions/SKILL.md)
- Database calls deep inside logic -> [functional-parameterizing-dependencies](../functional-parameterizing-dependencies/SKILL.md)
- Errors thrown and caught everywhere -> [functional-handling-errors-with-results](../functional-handling-errors-with-results/SKILL.md)
- Error handling dwarfs the happy path -> [functional-defining-errors-out-of-existence](../functional-defining-errors-out-of-existence/SKILL.md)
- Logic cannot be tested without I/O -> [functional-separating-pure-core-from-shell](../functional-separating-pure-core-from-shell/SKILL.md)
- Shared mutable state or race conditions -> [functional-managing-state-immutably](../functional-managing-state-immutably/SKILL.md)
- Wire and storage shapes leak inward -> [functional-crossing-io-boundaries](../functional-crossing-io-boundaries/SKILL.md)
- Reaching for a class-based pattern -> [functional-translating-gof-patterns](../functional-translating-gof-patterns/SKILL.md)
- Loops accumulating into variables -> [functional-folding-over-data](../functional-folding-over-data/SKILL.md)
- Deep recursion or expensive repeats -> [functional-using-recursion-and-laziness](../functional-using-recursion-and-laziness/SKILL.md)
- Names are vague or hard to choose -> [functional-choosing-precise-names](../functional-choosing-precise-names/SKILL.md)
- Comments restate the code -> [functional-writing-useful-comments](../functional-writing-useful-comments/SKILL.md)
- Tests need heavy mocking -> [functional-testing-functional-code](../functional-testing-functional-code/SKILL.md)
- Need to judge a design or a diff -> [functional-reviewing-functional-design](../functional-reviewing-functional-design/SKILL.md)
- Imperative code to be moved forward -> [functional-refactoring-toward-functional-design](../functional-refactoring-toward-functional-design/SKILL.md)
- Effects retried, duplicated, or lost -> [functional-making-effects-reliable](../functional-making-effects-reliable/SKILL.md)
- An incident the records could not explain -> [functional-designing-what-to-observe](../functional-designing-what-to-observe/SKILL.md)

## Language packs

Core skills use a neutral notation. For real syntax, load the pack that
matches the project and use it alongside the core skill.

- [functional-javascript](../functional-javascript/SKILL.md)
- [functional-typescript](../functional-typescript/SKILL.md)
- [functional-typescript-effect](../functional-typescript-effect/SKILL.md)
- [functional-typescript-ts-pattern](../functional-typescript-ts-pattern/SKILL.md)
- [functional-typescript-xstate](../functional-typescript-xstate/SKILL.md)
- [functional-typescript-react](../functional-typescript-react/SKILL.md)
- [functional-typescript-react-nextjs](../functional-typescript-react-nextjs/SKILL.md)
- [functional-elixir](../functional-elixir/SKILL.md)
- [functional-elixir-phoenix](../functional-elixir-phoenix/SKILL.md)

## Quick reference

| Question                     | Answer                                  |
| ---------------------------- | --------------------------------------- |
| Where does validation go?    | At the boundary, into domain types      |
| Where does I/O go?           | Edges only; core stays pure             |
| How are errors returned?     | Result values, not exceptions           |
| How does the core get data?  | A handler reads it, and passes values   |
| What makes an interface good | It hides far more than it reveals       |
| Does this shape need a type? | Yes, unless a listed fact is true of it |
| When is a design done?       | When the red-flag audit finds none      |

The facts in the shape row are listed in
[functional-choosing-types-or-plain-data](../functional-choosing-types-or-plain-data/SKILL.md).

## Red flags

Each is a place to look, not a verdict. Where this codebase states a rule
of its own, that rule is the answer; elsewhere a flag becomes a finding
only with the evidence listed beside it.

- A design decision was made before anyone named the domain concept.
  Evidence: a name in the diff that the specification, the ticket, and
  the glossary do not use.
- A type can represent a state the business forbids. Evidence: a value
  of that type that a stated rule refuses, built in a test or an example.
- A function's signature does not say what it can fail with. Evidence: a
  `throw`, a null return, or a swallowed error in the body that the
  signature does not show.
- A module's interface is as complicated as its implementation.
  Evidence: the exported signatures and their documentation take about
  as many lines as the code behind them.
- Only one design was considered. Evidence: a new module or workflow
  whose design notes or pull request text list no alternative. A change
  that follows the module's existing idiom needs no alternative.

## Further reading

- [notation.md](references/notation.md) is the neutral notation used by
  every core skill in this pack.
- [design-loop.md](references/design-loop.md) is a worked pass through
  the design loop on one small feature.
- [principles.md](references/principles.md) is a page of the main
  rules in the pack, for when there is no time to read a skill.
- [calibration.md](references/calibration.md) is how much of the pack a
  given task actually warrants, and the cost of getting that wrong.
- [working-alone.md](references/working-alone.md) is what to do where a
  step asks for an expert, a reviewer, or an owner and there is none to
  ask.
- [project-setup.md](references/project-setup.md) is a block for a
  project's `CLAUDE.md` that states what outranks what and when the
  pack applies to work, not only to questions.
- [essential-and-accidental.md](../functional-diagnosing-complexity/references/essential-and-accidental.md)
  is the test that finds candidates for removal before any refactor: did
  we add this complexity, or is it in the problem?

## Worked examples

Five end-to-end walkthroughs. Judgment transfers through these better
than through rules.

- [design-loop.md](references/design-loop.md) — a small feature through
  the design loop.
- [a new workflow](../functional-designing-workflow-pipelines/references/worked-example.md)
  — booking an appointment, from event storming to composed code.
- [a refactor](../functional-refactoring-toward-functional-design/references/worked-refactor.md)
  — a legacy service moved through the ten moves.
- [a read model](../functional-crossing-io-boundaries/references/worked-read-model.md)
  — a screen where the aggregate rules do not apply.
- [a long-running process](../functional-making-effects-reliable/references/worked-long-running.md)
  — three systems, compensation, and idempotency.

Files in this skill

  • SKILL.md16.3 KB
  • references/calibration.md6 KB
  • references/design-loop.md3.9 KB
  • references/notation.md13 KB
  • references/principles.md9 KB
  • references/project-setup.md1.9 KB
  • references/working-alone.md2.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.