Flip Arguments
Flip Arguments
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) => voidSolution
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
: neverAnalysis
The problem breaks into two clear sub-problems:
- Reverse a tuple — flip the order of elements in a parameter list
- 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:
- Extract the last element (
Last) and the rest (Init) usinginfer - Build the result by putting
Lastfirst, then recursing onInit - Base case: empty array → return
[]
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
: neverUsing 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
: neverThe 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
[...infer Init, infer Last]extracts the tail of a tuple — useful for reversed iteration[infer First, ...infer Rest]extracts the head — useful for forward iteration- Combining
inferon arguments + return type lets you surgically transform function signatures - Building a
Reverseutility type is a common pattern worth keeping in your TypeScript toolkit
