Asher Cohen
Back to posts

Naming Callback Functions: Best Practices

Clear naming conventions for callback functions that make your code self-documenting

Introduction

Naming things is one of the hardest problems in programming. This is especially true for callback functions. Good names make code self-documenting; poor names create confusion.

The Problem with Anonymous Callbacks

Consider this common pattern:

// ❌ What does this do?
const arr = [1, 2, 3].map((a) => a * 2);

The callback is anonymous and the parameter name a tells us nothing. We have to read the implementation to understand the intent.

The Solution: Named Callbacks

// ✅ Clear intent
const double = (n) => n * 2;
const arr = [1, 2, 3].map(double);

Now we immediately know what's happening. The code reads like English.

Naming Patterns by Use Case

Array Methods

// Transformation
const toUpperCase = (str) => str.toUpperCase();
const names.map(toUpperCase);

// Filtering
const isActive = (user) => user.status === 'active';
const users.filter(isActive);

// Finding
const isAdult = (person) => person.age >= 18;
const people.find(isAdult);

// Reduction
const sum = (acc, val) => acc + val;
const numbers.reduce(sum, 0);

Event Handlers

// ❌ Unclear
button.addEventListener('click', (e) => {
  handleSubmit(e);
});

// ✅ Clear
const handleClick = (e) => {
  handleSubmit(e);
};
button.addEventListener('click', handleClick);

Async Operations

// ❌ Anonymous callback
fetch('/api/data')
  .then((res) => res.json())
  .then((data) => {
    console.log(data);
  });

// ✅ Named callbacks
const parseJson = (res) => res.json();
const logData = (data) => console.log(data);

fetch('/api/data').then(parseJson).then(logData);

Naming Conventions

Use Verbs for Actions

const validate = (input) => {
  /* ... */
};
const format = (date) => {
  /* ... */
};
const calculate = (items) => {
  /* ... */
};

Use Predicates for Boolean Returns

Prefix with is, has, can, should:

const isValid = (email) => email.includes('@');
const hasPermission = (user) => user.role === 'admin';
const canEdit = (user, post) => user.id === post.authorId;

Use Descriptive Parameter Names

// ❌ Unclear
const process = (x) => x * 2;

// ✅ Clear
const calculateTotal = (price) => price * 2;

When Anonymous Callbacks Are Okay

Trivial Operations

// Simple enough to be inline
const doubled = numbers.map((n) => n * 2);

One-Time Use

// Used only once, clear from context
setTimeout(() => {
  console.log('Done!');
}, 1000);

Arrow Functions with Obvious Intent

// Clear from the method name
const hasAdults = people.some((person) => person.age >= 18);

Common Mistakes

Generic Names

// ❌ Too generic
const callback = (data) => {
  /* ... */
};
const handler = (e) => {
  /* ... */
};

// ✅ Specific
const handleUserResponse = (data) => {
  /* ... */
};
const handleButtonClick = (e) => {
  /* ... */
};

Implementation Details in Names

// ❌ Describes how, not what
const loopAndCalculate = (items) => {
  /* ... */
};

// ✅ Describes what
const calculateTotal = (items) => {
  /* ... */
};

Overly Long Names

// ❌ Too verbose
const calculateTotalPriceIncludingTaxAndDiscount = (order) => {
  /* ... */
};

// ✅ Just right
const calculateOrderTotal = (order) => {
  /* ... */
};

Benefits of Named Callbacks

  1. Reusability: Extract and reuse the function elsewhere
  2. Testability: Test the callback independently
  3. Readability: Code reads like a story
  4. Debugging: Named functions appear in stack traces
  5. Documentation: The name explains the intent

Conclusion

Take the extra few seconds to name your callbacks. Your future self—and your teammates—will thank you. Clear names turn cryptic code into self-documenting stories.

#javascript #clean-code #best-practices