FU

Write cleaner code using functional programming and immutable data patterns.

Install

mkdir -p .claude/skills/functional && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/3587" && unzip -o skill.zip -d .claude/skills/functional && rm skill.zip

Installs to .claude/skills/functional

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Functional programming patterns with immutable data. Use when writing logic, data transformations, or encountering mutation bugs. Covers immutability violations catalog, pure functions, composition, early returns, and options objects. Do NOT over-apply heavy FP abstractions (monads, fp-ts) unless the project requires them.
324 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Refactor loops into array methods
  • Implement immutable data structures
  • Compose small pure functions
  • Apply guard clauses for early returns

How it works

The skill guides the refactoring of imperative logic into pure functions and immutable data structures using declarative array methods and composition.

Inputs & outputs

You give it
Imperative code with state mutation
You get back
Refactored functional code using immutable structures

When to use functional

  • Refactor loops into functional array methods
  • Eliminate state mutation bugs
  • Compose functions for data pipelines
  • Implement immutable data structures

About this skill

Functional Patterns

Deep-dive resources are in the resources/ directory. Load them on demand:

ResourceLoad when...
immutability-catalog.mdFixing mutation bugs, applying readonly/ReadonlyArray types, or looking up the immutable alternative to an array/object mutation
composition-patterns.mdComposing small functions into pipelines, refactoring monolithic logic, or flattening deeply nested code

Small pure functions are an implementation technique, not a mandate to publish one function per module. Keep related helpers private and colocated when they compose into one coherent responsibility; use codebase-design when choosing the stable caller-facing contract.

Core Principles

  • Immutable domain data by default - keep local or boundary mutation encapsulated when it is clearer or required
  • Pure functions wherever possible
  • Composition over inheritance
  • Self-documenting code first - keep comments that explain constraints or non-obvious reasons
  • Array methods for transformations - use loops when control flow is clearer
  • Options objects for parameter groups - keep simple positional APIs simple

Why Immutability Matters

Immutable data is a foundation of functional programming. It makes code predictable (same input → same output, no hidden state changes), debuggable (state does not change underneath a reader), testable (less hidden mutable state), and React-friendly (reconciliation and memoization can rely on reference changes). It also reduces shared-state concurrency hazards, but does not by itself prevent races in I/O or coordination.

// ❌ WRONG - Mutation creates unpredictable behavior
const user = { name: 'Alice', permissions: ['read'] };
grantPermission(user, 'write'); // Mutates user.permissions internally
console.log(user.permissions); // ['read', 'write'] - SURPRISE! user changed

// ✅ CORRECT - Immutable approach is predictable
const updatedUser = grantPermission(user, 'write'); // Returns new object
console.log(user.permissions); // ['read'] - original unchanged
console.log(updatedUser.permissions); // ['read', 'write'] - new version

Use readonly on data that is intended to be immutable and ReadonlyArray<T> for immutable arrays so the compiler enforces that contract. Encapsulated mutable accumulators, caches, and adapter state are acceptable when they do not leak mutation into the domain contract. For common mutations and immutable alternatives, load resources/immutability-catalog.md.


Functional Light

Follow "Functional Light" principles - practical functional patterns without heavy abstractions:

  • DO: pure functions, immutable data, composition, declarative code, array methods, readonly type safety
  • DON'T: category theory, monads, heavy FP libraries (fp-ts, Ramda), over-engineering, functional for its own sake

Why: The goal is maintainable, testable code - not academic purity. If a functional pattern makes code harder to understand, don't use it.

// ✅ GOOD - Simple, clear, functional
const activeUsers = users.filter(u => u.active);
const userNames = activeUsers.map(u => u.name);

// ❌ OVER-ENGINEERED - Unnecessary abstraction
const compose = <T>(...fns: Array<(arg: T) => T>) => (x: T) =>
  fns.reduceRight((v, f) => f(v), x);
const withoutInactive = compose(
  (users: readonly User[]): readonly User[] => users.filter(u => u.active),
  (users: readonly User[]): readonly User[] => users.filter(u => !u.suspended),
)(users);

Self-Documenting Code and Useful Comments

Code should be clear through naming and structure. Prefer refactoring comments that merely narrate syntax, but keep comments that explain a non-obvious decision or constraint.

Comments worth keeping:

  • JSDoc for public APIs when generating documentation
  • "Why"-comments required by other skills: characterisation test file headers and SUSPICIOUS behavior markers (see the characterisation-tests skill)
  • Constraints the code cannot express (e.g. a workaround pinned to an upstream bug, an ordering requirement imposed by an external system)

WRONG - Comments explaining unclear code

// Get the user and check if active and has permission
function check(u: any) {
  // Check user exists, then active, then permission
  if (u) {
    if (u.a) {
      if (u.p) return true;
    }
  }
  return false;
}

CORRECT - Self-documenting code

function canUserAccessResource(user: User | undefined): boolean {
  if (!user) return false;
  if (!user.isActive) return false;
  if (!user.hasPermission) return false;
  return true;
}

// Even better - a single boolean expression
function canUserAccessResource(user: User | undefined): boolean {
  return user !== undefined && user.isActive && user.hasPermission;
}

Check undefined explicitly in the boolean form: optional chaining (user?.isActive && user?.hasPermission) yields boolean | undefined and fails to compile under strict mode.

If a comment only restates what the code does, refactor instead: extract functions with descriptive names, use meaningful variable names, break complex logic into steps, or use type aliases for domain concepts.

Acceptable JSDoc for public APIs

/**
 * Registers a scenario for runtime switching.
 * @throws {ValidationError} if scenario ID is duplicate
 */
export function registerScenario(definition: ScenaristScenario): void {

Choosing Array Methods and Loops

Prefer map, filter, reduce for transformations. They're declarative (what, not how) and naturally immutable.

CORRECT - map, filter, reduce, and chaining

const scenarioIds = scenarios.map(s => s.id);
const activeScenarios = scenarios.filter(s => s.active);
const totalActiveMinutes = sessions
  .filter(session => session.active)
  .map(session => session.durationMinutes * session.repetitions)
  .reduce((sum, minutes) => sum + minutes, 0);

When Loops Are Acceptable

Imperative loops are fine when:

  • Early termination is essential (use for...of with break)
  • Performance critical (measure first!)
  • Side effects are necessary (logging, DOM manipulation)

Choose Array.find(), Array.some(), or Array.every() when those operations express the intent more directly; do not replace a clear loop merely to satisfy a style rule.


When to Use Options Objects

Use an options object when parameters form a meaningful group, several values share the same type, or optional arguments make ordering unclear. A small, stable function with obvious positional parameters can remain positional.

CORRECT - Options object

type CreateReportOptions = {
  reportId: string;
  format: 'pdf' | 'csv';
  locale: string;
  timeZone: string;
  includeCharts?: boolean;
  sendEmail?: boolean;
};

function createReport(options: CreateReportOptions): Report {
  const { reportId, format, locale, timeZone, includeCharts = false, sendEmail = true } = options;
  // ...
}

// Call site - crystal clear
createReport({ reportId: 'report_123', format: 'pdf', locale: 'en-GB', timeZone: 'Europe/London', includeCharts: true });

Use positional parameters when the order is obvious, as in add(a, b), or a familiar high-frequency utility would become noisier with an options object. Switch to named options when same-typed or optional arguments make a call ambiguous; parameter count is a signal, not a fixed limit.


Pure Functions

Pure functions have no side effects and always return the same output for the same input:

  1. No side effects - doesn't mutate external state, modify arguments, or perform I/O
  2. Deterministic - same input → same output; no dependency on Date.now(), Math.random(), or globals
  3. Referentially transparent - can replace the call with its return value

Pure functions are testable (no setup/teardown), composable, predictable, cacheable, and parallelizable.

When Impurity Is Necessary

Some functions must be impure (I/O, randomness, side effects). Isolate them:

// ✅ CORRECT - Isolate impure functions at edges
// Pure core
function calculateTotalWeightGrams(parcels: ReadonlyArray<Parcel>): number {
  return parcels.reduce((sum, parcel) => sum + parcel.weightGrams, 0);
}

// Impure shell (isolated)
async function saveShipment(shipment: Shipment): Promise<void> {
  const totalWeightGrams = calculateTotalWeightGrams(shipment.parcels); // Pure
  await database.save({ ...shipment, totalWeightGrams }); // Impure (I/O)
}

Pattern: Keep impure functions at system boundaries (adapters, ports). Keep core domain logic pure.


Early Returns Over Nesting

Treat deep nesting as a readability signal, not a numeric rule. When nested control flow obscures the main path, extract functions or flatten it with guard clauses. For worked examples, load resources/composition-patterns.md.

// ❌ WRONG - Nested conditions
if (user) {
  if (user.isActive) {
    if (user.hasPermission) {
      // do something
    }
  }
}

// ✅ CORRECT - Early returns (guard clauses)
if (!user) return;
if (!user.isActive) return;
if (!user.hasPermission) return;

// do something

Result Type for Error Handling

Use a Result type when expected failures are part of the caller-facing contract and callers must handle both branches. Preserve an established exception, nullable-value, or framework error convention when it communicates the contract more clearly.

type Result<T, E = Error> =
  | { readonly success: true; readonly data: T }
  | { readonly success: false; readonly error: E };

// Usage
function processBatch(batch: Batch): Result<BatchRun> {
  if (batch.itemCount <= 0) {
    return { success: false, error: new Error('Batch must contain an item') };
  }

  const run = executeBatch(batch);
  return { success: true, data: run };
}

// Caller handles bo

---

*Content truncated.*

When not to use it

  • When functional patterns make code harder to understand
  • Over-applying heavy FP abstractions like monads

Limitations

  • Avoid heavy FP libraries unless the project requires them

How it compares

It focuses on practical 'Functional Light' patterns to improve maintainability rather than academic category theory abstractions.

Compared to similar skills

functional side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
functional (this skill)32moNo flagsIntermediate
typescript-expert106moReviewAdvanced
agent-coder36moNo flagsIntermediate
modern-javascript-patterns24moNo flagsBeginner

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

typescript-expert

davila7

TypeScript and JavaScript expert with deep knowledge of type-level programming, performance optimization, monorepo management, migration strategies, and modern tooling. Use PROACTIVELY for any TypeScript/JavaScript issues including complex type gymnastics, build performance, debugging, and architectural decisions. If a specialized expert is a better fit, I will recommend switching and stop.

1029

agent-coder

ruvnet

Agent skill for coder - invoke with $agent-coder

315

modern-javascript-patterns

sickn33

Master ES6+ features including async/await, destructuring, spread operators, arrow functions, promises, modules, iterators, generators, and functional programming patterns for writing clean, efficient JavaScript code. Use when refactoring legacy code, implementing modern patterns, or optimizing JavaScript applications.

25

refactor

tygwan

Refactoring workflow. Improves code structure, reduces duplication, applies patterns. Use for code quality improvements.

00

typescript-lsp

Pouryaak

TypeScript language server providing type checking, code intelligence, and LSP diagnostics for .ts, .tsx, .js, .jsx, .mts, .cts, .mjs, .cjs files. Use when working with TypeScript or JavaScript code that needs type checking, autocomplete, error detection, refactoring support, or code navigation.

00

zustand

lobehub

Zustand state management guide. Use when working with store code (src/store/**), implementing actions, managing state, or creating slices. Triggers on Zustand store development, state management questions, or action implementation.

113434

Search skills

Search the agent skills registry