As TypeScript applications scale, data frequently passes through sequential transformations. While method chaining is prevalent in object-oriented paradigms, functional programming utilizes function composition. The pipe function serves as a core mechanism for this approach.
Without a pipe utility, applying multiple transformations typically requires nested function calls. This forces the code to be read from the inside out, which introduces unnecessary cognitive load and decreases maintainability.
const getPrice = (order: Order) => order.total;
const applyDiscount = (price: number) => price * 0.9;
const addTax = (price: number) => price * 1.25;
// Difficult to parse: execution order is inside-out.
const finalPrice = addTax(applyDiscount(getPrice(myOrder)));A pipe function accepts an initial value and processes it through a sequence of provided functions. The output of the preceding function serves as the input for the subsequent one. This establishes a linear, predictable execution flow.

The core implementation in JavaScript relies on the rest parameter syntax and the array reduce method.
// Processes an initial value through an array of functions.
const pipeLogic = (initialValue, ...fns) =>
fns.reduce((acc, fn) => fn(acc), initialValue);Establishing strict TypeScript definitions for a pipe function requires careful handling, as each function in the sequence may return a distinct type. The standard architectural approach involves utilizing function overloads to define the type transformations sequentially.
The following snippet illustrates a strictly typed pipe function supporting up to three composed functions. Additional overloads can be appended to support longer execution chains.
// 1. Function Overloads to define the strict input/output flow.
export function pipe<A, B>(value: A, fn1: (arg: A) => B): B;
export function pipe<A, B, C>(value: A, fn1: (arg: A) => B, fn2: (arg: B) => C): C;
export function pipe<A, B, C, D>(value: A, fn1: (arg: A) => B, fn2: (arg: B) => C, fn3: (arg: C) => D): D;
// 2. The implementation (typed with 'any' internally; the overloads govern the external API).
export function pipe(value: any, ...fns: Function[]): any {
return fns.reduce((acc, fn) => fn(acc), value);
}Implementing the refactored pricing logic with the typed pipe function demonstrates its utility. TypeScript resolves the types continuously through the execution chain.
const myOrder = { total: 100 };
// Linear, top-to-bottom evaluation.
// A type mismatch between functions will trigger a compiler error.
const finalPrice = pipe(
myOrder,
getPrice,
applyDiscount,
addTax
);
console.log(finalPrice); // 112.5Implementing a custom pipe function provides practical exposure to function composition and advanced TypeScript type resolution.