Prefer Named Functions Over Anonymous Functions
Goal
When defining a function, prefer a named function over an anonymous one, regardless of the technology stack.
A named function is any callable whose definition is bound to an explicit, descriptive identifier visible at its definition site (a function declaration, a named function expression, an arrow function or lambda assigned to a const/val/let with a meaningful name, a named method, or a named local helper). An anonymous function is a callable defined inline without a stable, descriptive identifier — typically a lambda, arrow function, block, or function expression passed directly as an argument or returned without being bound to a name.
Named functions improve stack traces, code navigation, searchability, reuse, testability, and the reader's ability to understand intent without parsing the body. Anonymous functions are still appropriate when the stack expects them or when naming would actively hurt readability.
What Counts as In Scope
Apply this skill to code that does one or more of these things:
- defines a callback, handler, listener, mapper, predicate, comparator, reducer, effect, or task as an inline anonymous function
- assigns an anonymous function or lambda to a variable without a meaningful name
- passes an inline lambda or arrow function with non-trivial logic as an argument
- defines an event handler, route handler, middleware, or job as an anonymous function
- exports an anonymous function or anonymous default export from a module
- repeats the same anonymous function shape in multiple places that could share a named definition
The Rule
Default to a named function for every function you define.
- Use the language's idiomatic naming construct: function declaration, named function expression, named lambda binding, named method, or named local helper.
- Choose a name that describes the function's intent in domain terms, not its mechanics.
Bind anonymous shapes to a descriptive name when they have non-trivial logic.
- If a callback, handler, or lambda contains more than a single trivial expression, lift it to a named function or a named local binding before passing it.
- Prefer extracting it to module scope or a clearly named local constant over inlining it.
Avoid anonymous default exports and anonymous module-level functions.
- Export functions under explicit names so call sites, stack traces, and tooling can identify them.
- Do not rely on the importing module to give the function a name.
Use anonymous functions only when one of the documented exceptions applies.
- The exception must be specific and justifiable, not a stylistic default.
- When in doubt, name it.
When Anonymous Functions Are Appropriate
Anonymous functions are appropriate when at least one of these holds:
- The stack is designed around anonymous callables. The language, framework, or API is built such that anonymous functions are the natural, idiomatic, or required form. Examples include single-expression collection operations (
map, filter, reduce), trailing lambdas in DSLs, structural shorthand like Kotlin's trailing lambda, Ruby blocks, Scala for-comprehensions, SAM conversions, or framework hooks that explicitly expect inline closures.
- The function body is a trivial, self-evident expression. A one-line transformation, projection, or predicate where a name would only restate the body (e.g.,
users.map(u => u.id), items.filter(i => i.active)).
- Naming would actively hurt readability. Introducing a name forces the reader to jump elsewhere to understand a one-shot, locally scoped operation whose meaning is obvious from context.
- The callable is a single-use, locally scoped continuation. A short closure that captures local state, runs once, and has no meaning outside its call site.
- The framework or runtime requires an anonymous form. The API contract only accepts inline lambdas, blocks, or anonymous classes (e.g., certain reactive operators, effect runners, or platform callbacks where naming is impossible or unidiomatic).
- Project conventions consistently use anonymous functions for this construct. The codebase has an established, deliberate convention and switching to named forms would introduce inconsistency or friction.
In all these cases, keep the anonymous body small and focused. If it grows beyond a trivial expression, lift it to a named function.
Examples
Prefer this:
function isActiveAdult(user: User): boolean {
return user.isActive && user.age >= 18;
}
const activeAdults = users.filter(isActiveAdult);
def is_active_adult(user: User) -> bool:
return user.is_active and user.age >= 18
active_adults = [u for u in users if is_active_adult(u)]
fun isActiveAdult(user: User): Boolean = user.isActive && user.age >= 18
val activeAdults = users.filter(::isActiveAdult)
Avoid this when the body is non-trivial:
const activeAdults = users.filter((u) => {
const meetsAge = u.age >= 18;
const isVerified = u.verifiedAt !== null;
return u.isActive && meetsAge && isVerified;
});
export default (req, res) => {
// anonymous default export — invisible in stack traces and imports
};
Anonymous is fine when the body is trivial and idiomatic:
const ids = users.map((u) => u.id);
const active = users.filter((u) => u.isActive);
ids = [u.id for u in users]
val ids = users.map { it.id }
Anonymous is fine when the stack expects it:
button.setOnClickListener { view -> handleClick(view) }
useEffect(() => {
subscribe();
return () => unsubscribe();
}, []);
Detection Workflow
Identify anonymous callables in the changed or reviewed code.
- Look for inline lambdas, arrow functions, function expressions, blocks, anonymous classes, and anonymous default exports.
- Note any callable whose definition site has no descriptive identifier.
Classify each anonymous callable.
- Trivial single-expression body in an idiomatic collection or framework call: leave as is.
- Required by the framework or runtime contract: leave as is.
- Established project convention: respect it.
- Otherwise: candidate for renaming or extraction.
Check stack traces, logs, and tooling impact.
- Anonymous functions often appear as
<anonymous>, lambda, <lambda>, or generated names in errors and profilers.
- If the function participates in async flows, error reporting, or hot paths, prefer a name to aid debugging.
Look for repeated anonymous shapes.
- The same anonymous predicate, mapper, or handler used in multiple places is a strong signal to extract a single named function.
Writing or Changing Functions
Name first.
- When you write a new function, give it an explicit, descriptive name in the language's idiomatic form.
- Place it at the smallest scope where it is reusable and discoverable.
Extract when bodies grow.
- If an anonymous callback grows beyond a trivial expression, extract it to a named function before continuing.
- Keep the call site readable:
users.filter(isActiveAdult) over a multi-line inline lambda.
Replace anonymous default exports with named exports.
- Export the function under an explicit name and let the importer use the same name.
Preserve idiomatic anonymous use.
- Do not rewrite
users.map((u) => u.id) into a named helper just to satisfy the rule.
- Do not name closures that the framework expects to be anonymous and that have no meaning outside the call site.
Match project conventions.
- If the codebase has a consistent, deliberate convention for a given construct (e.g., always-anonymous route handlers in a specific framework), follow it.
- If the convention is accidental or inconsistent, prefer named forms going forward.
Review Questions
When reading or reviewing code, ask:
- Is this callable anonymous when a named function would work?
- Does its body go beyond a trivial, self-evident expression?
- Would a name help stack traces, navigation, search, or reuse?
- Is the anonymous form genuinely required or strongly favored by the stack, framework, or local readability?
- Is the same anonymous shape repeated in multiple places that could share a named definition?
- Is this an anonymous default export or anonymous module-level function that should be named?
If a callable is anonymous without a clear reason, apply this skill.
Report the Outcome
When finishing the task:
- state which anonymous callables were identified, named, or extracted
- state which anonymous callables were intentionally left anonymous and why (stack idiom, trivial body, framework requirement, or project convention)
- state whether project conventions or stack constraints influenced the choice
1---2name: prefer-named-functions3description: Prefer named functions over anonymous functions across any technology stack. Use when an agent needs to define, refactor, or review functions, methods, callbacks, handlers, lambdas, arrow functions, or function expressions. Choose a named function unless the language, framework, idiomatic style, or local readability genuinely favors an anonymous form.4---56# Prefer Named Functions Over Anonymous Functions78## Goal910When defining a function, prefer a named function over an anonymous one, regardless of the technology stack.1112A named function is any callable whose definition is bound to an explicit, descriptive identifier visible at its definition site (a function declaration, a named function expression, an arrow function or lambda assigned to a `const`/`val`/`let` with a meaningful name, a named method, or a named local helper). An anonymous function is a callable defined inline without a stable, descriptive identifier — typically a lambda, arrow function, block, or function expression passed directly as an argument or returned without being bound to a name.1314Named functions improve stack traces, code navigation, searchability, reuse, testability, and the reader's ability to understand intent without parsing the body. Anonymous functions are still appropriate when the stack expects them or when naming would actively hurt readability.1516## What Counts as In Scope1718Apply this skill to code that does one or more of these things:1920- defines a callback, handler, listener, mapper, predicate, comparator, reducer, effect, or task as an inline anonymous function21- assigns an anonymous function or lambda to a variable without a meaningful name22- passes an inline lambda or arrow function with non-trivial logic as an argument23- defines an event handler, route handler, middleware, or job as an anonymous function24- exports an anonymous function or anonymous default export from a module25- repeats the same anonymous function shape in multiple places that could share a named definition2627## The Rule28291. Default to a named function for every function you define.30 - Use the language's idiomatic naming construct: function declaration, named function expression, named lambda binding, named method, or named local helper.31 - Choose a name that describes the function's intent in domain terms, not its mechanics.32332. Bind anonymous shapes to a descriptive name when they have non-trivial logic.34 - If a callback, handler, or lambda contains more than a single trivial expression, lift it to a named function or a named local binding before passing it.35 - Prefer extracting it to module scope or a clearly named local constant over inlining it.36373. Avoid anonymous default exports and anonymous module-level functions.38 - Export functions under explicit names so call sites, stack traces, and tooling can identify them.39 - Do not rely on the importing module to give the function a name.40414. Use anonymous functions only when one of the documented exceptions applies.42 - The exception must be specific and justifiable, not a stylistic default.43 - When in doubt, name it.4445## When Anonymous Functions Are Appropriate4647Anonymous functions are appropriate when at least one of these holds:4849- **The stack is designed around anonymous callables.** The language, framework, or API is built such that anonymous functions are the natural, idiomatic, or required form. Examples include single-expression collection operations (`map`, `filter`, `reduce`), trailing lambdas in DSLs, structural shorthand like Kotlin's trailing lambda, Ruby blocks, Scala for-comprehensions, SAM conversions, or framework hooks that explicitly expect inline closures.50- **The function body is a trivial, self-evident expression.** A one-line transformation, projection, or predicate where a name would only restate the body (e.g., `users.map(u => u.id)`, `items.filter(i => i.active)`).51- **Naming would actively hurt readability.** Introducing a name forces the reader to jump elsewhere to understand a one-shot, locally scoped operation whose meaning is obvious from context.52- **The callable is a single-use, locally scoped continuation.** A short closure that captures local state, runs once, and has no meaning outside its call site.53- **The framework or runtime requires an anonymous form.** The API contract only accepts inline lambdas, blocks, or anonymous classes (e.g., certain reactive operators, effect runners, or platform callbacks where naming is impossible or unidiomatic).54- **Project conventions consistently use anonymous functions for this construct.** The codebase has an established, deliberate convention and switching to named forms would introduce inconsistency or friction.5556In all these cases, keep the anonymous body small and focused. If it grows beyond a trivial expression, lift it to a named function.5758## Examples5960Prefer this:6162```ts63function isActiveAdult(user: User): boolean {64 return user.isActive && user.age >= 18;65}6667const activeAdults = users.filter(isActiveAdult);68```6970```py71def is_active_adult(user: User) -> bool:72 return user.is_active and user.age >= 187374active_adults = [u for u in users if is_active_adult(u)]75```7677```kt78fun isActiveAdult(user: User): Boolean = user.isActive && user.age >= 187980val activeAdults = users.filter(::isActiveAdult)81```8283Avoid this when the body is non-trivial:8485```ts86const activeAdults = users.filter((u) => {87 const meetsAge = u.age >= 18;88 const isVerified = u.verifiedAt !== null;89 return u.isActive && meetsAge && isVerified;90});91```9293```js94export default (req, res) => {95 // anonymous default export — invisible in stack traces and imports96};97```9899Anonymous is fine when the body is trivial and idiomatic:100101```ts102const ids = users.map((u) => u.id);103const active = users.filter((u) => u.isActive);104```105106```py107ids = [u.id for u in users]108```109110```kt111val ids = users.map { it.id }112```113114Anonymous is fine when the stack expects it:115116```kt117button.setOnClickListener { view -> handleClick(view) }118```119120```ts121useEffect(() => {122 subscribe();123 return () => unsubscribe();124}, []);125```126127## Detection Workflow1281291. Identify anonymous callables in the changed or reviewed code.130 - Look for inline lambdas, arrow functions, function expressions, blocks, anonymous classes, and anonymous default exports.131 - Note any callable whose definition site has no descriptive identifier.1321332. Classify each anonymous callable.134 - Trivial single-expression body in an idiomatic collection or framework call: leave as is.135 - Required by the framework or runtime contract: leave as is.136 - Established project convention: respect it.137 - Otherwise: candidate for renaming or extraction.1381393. Check stack traces, logs, and tooling impact.140 - Anonymous functions often appear as `<anonymous>`, `lambda`, `<lambda>`, or generated names in errors and profilers.141 - If the function participates in async flows, error reporting, or hot paths, prefer a name to aid debugging.1421434. Look for repeated anonymous shapes.144 - The same anonymous predicate, mapper, or handler used in multiple places is a strong signal to extract a single named function.145146## Writing or Changing Functions1471481. Name first.149 - When you write a new function, give it an explicit, descriptive name in the language's idiomatic form.150 - Place it at the smallest scope where it is reusable and discoverable.1511522. Extract when bodies grow.153 - If an anonymous callback grows beyond a trivial expression, extract it to a named function before continuing.154 - Keep the call site readable: `users.filter(isActiveAdult)` over a multi-line inline lambda.1551563. Replace anonymous default exports with named exports.157 - Export the function under an explicit name and let the importer use the same name.1581594. Preserve idiomatic anonymous use.160 - Do not rewrite `users.map((u) => u.id)` into a named helper just to satisfy the rule.161 - Do not name closures that the framework expects to be anonymous and that have no meaning outside the call site.1621635. Match project conventions.164 - If the codebase has a consistent, deliberate convention for a given construct (e.g., always-anonymous route handlers in a specific framework), follow it.165 - If the convention is accidental or inconsistent, prefer named forms going forward.166167## Review Questions168169When reading or reviewing code, ask:170171- Is this callable anonymous when a named function would work?172- Does its body go beyond a trivial, self-evident expression?173- Would a name help stack traces, navigation, search, or reuse?174- Is the anonymous form genuinely required or strongly favored by the stack, framework, or local readability?175- Is the same anonymous shape repeated in multiple places that could share a named definition?176- Is this an anonymous default export or anonymous module-level function that should be named?177178If a callable is anonymous without a clear reason, apply this skill.179180## Report the Outcome181182When finishing the task:183184- state which anonymous callables were identified, named, or extracted185- state which anonymous callables were intentionally left anonymous and why (stack idiom, trivial body, framework requirement, or project convention)186- state whether project conventions or stack constraints influenced the choice