Skip to content

Strongly Typed pipe()/compose() (Variadic Tuples vs Overloads)

How to type function pipelines like a library author: overloads, variadic tuples, inference boundaries, and what interviewers really want to see.

Rajesh DhimanMarch 01, 20262 min read
On this page

What you’ll learn

  • How to type a function pipeline so each step is type-safe
  • Two production patterns: overloads and variadic tuples
  • Where inference breaks (and how to design around it)

30‑second interview answer

To type pipe(a, f1, f2, f3) you need TypeScript to infer the output of each function as the input of the next. The common approach is either (1) a small set of overloads (simple + readable) or (2) variadic tuple types (more generic, more complex). In interviews, the key is showing you understand type inference boundaries and can choose the maintainable option.


The goal

We want this to be safe:

const result = pipe(
  "  hello world  ",
  s => s.trim(),
  s => s.toUpperCase(),
  s => s.split(" ")
);
// result: string[]

…and this to be a type error:

pipe("x", s => s.length, n => n.toUpperCase());
//                          ^ number has no toUpperCase

Approach 1: Overloads (most production teams should do this)

Overloads are boring and work great.

type Unary<A, R> = (arg: A) => R;
 
export function pipe<A, B>(a: A, ab: Unary<A, B>): B;
export function pipe<A, B, C>(a: A, ab: Unary<A, B>, bc: Unary<B, C>): C;
export function pipe<A, B, C, D>(
  a: A,
  ab: Unary<A, B>,
  bc: Unary<B, C>,
  cd: Unary<C, D>
): D;
export function pipe(a: unknown, ...fns: Array<Unary<any, any>>) {
  return fns.reduce((v, fn) => fn(v), a);
}

Why overloads are a good default

  • Easy to read
  • Great error messages
  • You control complexity (add overloads up to N steps)

Approach 2: Variadic tuples (library-author mode)

This is harder, but can type arbitrary-length pipelines.

A pragmatic version is:

type Fn = (a: any) => any;
 
type PipeResult<Input, Fns extends Fn[]> =
  Fns extends []
    ? Input
    : Fns extends [(a: infer A) => infer B, ...infer Rest]
      ? Input extends A
        ? Rest extends Fn[]
          ? PipeResult<B, Rest>
          : never
        : never
      : never;
 
export function pipe<Input, Fns extends Fn[]>(
  input: Input,
  ...fns: Fns
): PipeResult<Input, Fns> {
  return fns.reduce((v, fn) => fn(v), input) as any;
}

Trade-offs

  • Harder to maintain
  • Error messages can become cryptic
  • Runtime is still simple, types are the complicated part

Where inference breaks (important)

Type inference struggles when:

  • You pass functions with overloaded signatures
  • You build arrays of functions dynamically
  • You use generics that need explicit type parameters

Production pattern: if you need dynamic pipelines, you often accept a weaker type and validate at boundaries.


Production rule of thumb

  • For apps: use overloads up to 4–8 steps.
  • For libraries: consider variadic tuples, but prioritize developer experience (clear errors) over cleverness.

Interview questions

  1. Q: Why are overloads common even though variadic tuples exist?

    • A: They’re simpler, with clearer errors, and cover most real use cases.
  2. Q: What’s the hardest part of typing pipe?

    • A: Ensuring each function’s output matches the next function’s input via inference.

Quick recap

  • pipe typing is about chaining inference.
  • Overloads are often the best engineering choice.
  • Variadic tuples are powerful but come with complexity cost.
Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.