The Single Responsibility Principle in Functional Programming: A Practical Guide
Maxim Logunov

Maxim Logunov @maximlogunov

About: Senior Frontend Developer | React & TypeScript Expert

Joined:
Feb 25, 2025

The Single Responsibility Principle in Functional Programming: A Practical Guide

Publish Date: Apr 10
0 0

Introduction

The Single Responsibility Principle (SRP) is a powerful concept that transcends programming paradigms. In functional programming, it leads to code that's more maintainable, testable, and composable. Let's explore how to apply SRP effectively through concrete examples.

Understanding SRP in Functional Terms

A function with single responsibility:

  • Has one clear purpose
  • Performs a single transformation
  • Can be described without using "and"
  • Changes for only one reason

Well-Designed Function

// Does one thing: calculates area
const calculateCircleArea = radius => Math.PI * radius ** 2;
Enter fullscreen mode Exit fullscreen mode

Poorly Designed Function

// Does too much: validates AND calculates AND formats
function processCircle(radius) {
  if (typeof radius !== 'number') throw new Error('Invalid radius');
  const area = Math.PI * radius ** 2;
  return `The area is ${area.toFixed(2)}`;
}
Enter fullscreen mode Exit fullscreen mode

When to Apply SRP Strictly

1. Business Logic

// Pure functions for financial calculations
const calculateMonthlyPayment = (principal, rate, term) => {
  const monthlyRate = rate / 12 / 100;
  return (principal * monthlyRate) / 
         (1 - Math.pow(1 + monthlyRate, -term));
};

const calculateTotalInterest = (payment, term, principal) => {
  return payment * term - principal;
};
Enter fullscreen mode Exit fullscreen mode

2. Data Transformations

// Single-purpose data processors
const normalizeName = name => name.trim().toLowerCase();
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
const formatUserName = compose(capitalize, normalizeName);
Enter fullscreen mode Exit fullscreen mode

3. Validation Logic

// Focused validation functions
const isEmailValid = email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const isPasswordStrong = password => password.length >= 8;
const validateCredentials = (email, password) => 
  isEmailValid(email) && isPasswordStrong(password);
Enter fullscreen mode Exit fullscreen mode

4. Pure Utility Functions

// Reusable utilities with single responsibility
const toDollars = amount => `$${amount.toFixed(2)}`;
const truncate = (str, max) => 
  str.length > max ? `${str.slice(0, max)}...` : str;
Enter fullscreen mode Exit fullscreen mode

When to Relax SRP

1. Performance Optimization

// Combined operation for efficiency
function analyzeArray(arr) {
  return arr.reduce((stats, num) => {
    stats.sum += num;
    stats.min = Math.min(stats.min, num);
    stats.max = Math.max(stats.max, num);
    return stats;
  }, { sum: 0, min: Infinity, max: -Infinity });
}
Enter fullscreen mode Exit fullscreen mode

2. Simple Data Processing

// Reasonable combination for simple cases
function formatProduct(product) {
  return {
    id: product.id,
    name: product.name.trim(),
    price: `$${product.price.toFixed(2)}`,
    inStock: product.quantity > 0
  };
}
Enter fullscreen mode Exit fullscreen mode

3. Domain Operations

// Combined domain-specific action
function placeOrder(cart, payment) {
  const subtotal = calculateSubtotal(cart.items);
  const tax = calculateTax(subtotal);
  const order = createOrder(cart, payment, subtotal + tax);
  return processPayment(order);
}
Enter fullscreen mode Exit fullscreen mode

4. Prototyping

// Quick prototype version
function analyzeText(text) {
  const words = text.split(/\s+/);
  return {
    wordCount: words.length,
    avgLength: words.reduce((sum, w) => sum + w.length, 0) / words.length,
    sentences: text.split(/[.!?]+/).length
  };
}
Enter fullscreen mode Exit fullscreen mode

Composing Single-Purpose Functions

The real power emerges when combining small, focused functions:

// Small building blocks
const filterActive = users => users.filter(u => u.isActive);
const sortByDate = users => [...users].sort((a, b) => b.joined - a.joined);
const limit = n => users => users.slice(0, n);

// Composed pipeline
const getRecentActiveUsers = compose(
  limit(5),
  sortByDate,
  filterActive
);
Enter fullscreen mode Exit fullscreen mode

Refactoring to SRP

Here's how to transform a multi-responsibility function:

// Before
function processUser(user) {
  if (!user.name || !user.email) throw new Error('Invalid user');

  const formattedName = user.name.trim().toUpperCase();
  const formattedEmail = user.email.trim().toLowerCase();

  return {
    ...user,
    name: formattedName,
    email: formattedEmail,
    welcomeMessage: `Hello ${formattedName}!`
  };
}

// After
const validateUser = user => {
  if (!user.name || !user.email) throw new Error('Invalid user');
  return user;
};

const formatName = name => name.trim().toUpperCase();
const formatEmail = email => email.trim().toLowerCase();
const createWelcome = name => `Hello ${name}!`;

const processUser = user => {
  const validUser = validateUser(user);
  const name = formatName(validUser.name);
  const email = formatEmail(validUser.email);

  return {
    ...validUser,
    name,
    email,
    welcomeMessage: createWelcome(name)
  };
};
Enter fullscreen mode Exit fullscreen mode

Finding the Right Balance

Effective functional programming with SRP means:

  1. Start small: Create focused functions for complex logic
  2. Compose thoughtfully: Build larger operations from small pieces
  3. Be pragmatic: Combine simple operations when it improves clarity
  4. Refactor when needed: Split functions as responsibilities emerge

Remember that SRP serves the larger goals of maintainability and reliability. The best functional code balances separation of concerns with practical readability.

Comments 0 total

    Add comment