Yuzhe's Blog

yuzhes

Flip Arguments

Flip Arguments

Challenge Link

Challenge

Implement the type version of lodash’s _.flip.

Type FlipArguments<T> requires a function type T and returns a new function type with the same return type but with the argument order reversed.

type Flipped = FlipArguments<(a: string, b: number, c: boolean) => void>
// (a: boolean, b: number, c: string) => void

Solution

type Reverse<T extends any[]> = T extends [...infer Init, infer Last]
  ? [Last, ...Reverse<Init>]
  : []

type FlipArguments<T extends (...args: any[]) => any> =
  T extends (...args: infer Args) => infer R
    ? (...args: Reverse<Args>) => R
    : never

Analysis

The problem breaks into two clear sub-problems:

  1. Reverse a tuple — flip the order of elements in a parameter list
  2. Reconstruct the function — wrap the reversed tuple back into a function signature

Step 1: Reversing a tuple

type Reverse<T extends any[]> = T extends [...infer Init, infer Last]
  ? [Last, ...Reverse<Init>]
  : []

This is a classic tail-recursive tuple reversal:

Example trace for Reverse<[string, number, boolean]>:

Reverse<[string, number, boolean]>
= [boolean, ...Reverse<[string, number]>]
= [boolean, number, ...Reverse<[string]>]
= [boolean, number, string, ...Reverse<[]>]
= [boolean, number, string]

Step 2: Reconstructing the function

type FlipArguments<T extends (...args: any[]) => any> =
  T extends (...args: infer Args) => infer R
    ? (...args: Reverse<Args>) => R
    : never

Using infer, we extract both the argument tuple Args and the return type R, then reconstruct the function with Reverse<Args> as the new parameter list.

Alternative Approach: Inline reversal

If you prefer not to define a separate Reverse helper, you can inline it with a slightly different technique using a second accumulator parameter:

type ReverseAcc<T extends any[], Acc extends any[] = []> =
  T extends [infer First, ...infer Rest]
    ? ReverseAcc<Rest, [First, ...Acc]>
    : Acc

type FlipArguments<T extends (...args: any[]) => any> =
  T extends (...args: infer Args) => infer R
    ? (...args: ReverseAcc<Args>) => R
    : never

The accumulator approach builds the reversed array head-first rather than tail-first, which can be slightly more readable and avoids deep spread operations.

Which is faster?

For TypeScript’s type checker, both are O(n) recursive steps. The accumulator version avoids the ...Reverse<Init> spread at each level, so it’s marginally more efficient for very long tuples. In practice the difference is negligible.

Edge Cases

// No arguments → empty tuple reversed is still empty
type T0 = FlipArguments<() => void>
// () => void ✓

// Single argument → unchanged
type T1 = FlipArguments<(a: string) => string>
// (a: string) => string ✓

// Two arguments → swapped
type T2 = FlipArguments<(a: string, b: number) => boolean>
// (a: number, b: string) => boolean ✓

Note: TypeScript preserves the parameter names from the spread types, not from the original. The names like a, b, c in the output are inherited from Reverse’s result tuple positions.

Key Takeaways