Part 3: Closures and Currying - Function Factories

Introduction

I built an authentication middleware system that needed to validate different user roles. My first attempt used global configuration variables and duplicate code for each role check. Adding a new role required copying and modifying entire validation functions. Configuration changes broke existing validators.

I refactored using closures and currying. Created factory functions that captured configuration in closures. Curried validators took role requirements first, then user data. Adding new roles meant calling the factory with different parameters. Configuration stayed local. That's when closures and currying clicked for me.

This article teaches you to use closures for encapsulation, curry functions for flexibility, build function factories, and create reusable patterns.

Understanding Closures

A closure gives a function access to its outer scope's variables, even after the outer function has returned.

Basic Closure

function createCounter() {
    let count = 0;  // Private variable
    
    return {
        increment: () => ++count,
        decrement: () => --count,
        getValue: () => count
    };
}

const counter = createCounter();

console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.getValue());   // 2
console.log(counter.decrement());  // 1

// Cannot access count directly
// console.log(counter.count);  // undefined

The increment, decrement, and getValue functions "close over" the count variable, keeping it alive and private.

Multiple Closures

Real Example: Configuration Management

Currying

Currying transforms a function with multiple arguments into a sequence of functions each taking a single argument.

Basic Currying

Currying with Arrow Functions

Partial Application

Partial application fixes some arguments, creating a new function.

Real Example: Validation Functions

Real Example: Authorization Middleware

Real Example: Logger Factory

Real Example: Query Builder

Currying Utility

Real Example: API Client Builder

Memoization with Closures

Best Practices

1. Use Closures for Private State

2. Curry for Reusability

3. Partial Application for Configuration

What's Next

You now understand:

  • Closures and lexical scope

  • Currying for flexible functions

  • Partial application

  • Function factories

  • Encapsulation with closures

  • Configuration through closures

In Part 4: Functional Error Handling, you'll learn to handle errors functionally using Either/Result types, Option/Maybe patterns, Railway-oriented programming, and composing error-handling logic.


Based on building production TypeScript systems with authentication, logging, validation, and API clients using closures and currying.

Last updated