Append Argument
Append Argument
Challenge
For given function type Fn, and any type A (any in this context means we don't restrict the type), create a generic type which will take Fn as the first argument, A as the second, and will produce function type G which will be the same as Fn but with appended argument A as a last one.
For example:
type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean>
// expected: (a: number, b: string, x: boolean) => numberSolution
type AppendArgument<Fn extends (...args: any[]) => any, A> =
Fn extends (...args: infer Args) => infer R
? (...args: [...Args, A]) => R
: neverThe key idea: infer the original parameters and return type, then rebuild the function signature with the new argument appended at the end.
Breaking it down
Step 1 — Constrain Fn to a callable
Fn extends (...args: any[]) => anyThis ensures Fn is a function type. Without this constraint, TypeScript won't let us perform function-type inference on it.
Step 2 — Extract params and return type via infer
Fn extends (...args: infer Args) => infer RArgscaptures the original parameter types as a tuple (e.g.,[number, string])Rcaptures the return type (e.g.,number)
Step 3 — Rebuild with A appended
(...args: [...Args, A]) => RSpreading Args and appending A into a new tuple gives us the extended parameter list. TypeScript happily accepts this as a valid rest parameter.
Walkthrough:
Fn = (a: number, b: string) => number
A = boolean
Args = [number, string]
R = number
Result = (...args: [number, string, boolean]) => number
= (a: number, b: string, x: boolean) => number ✓Deep Dive
Why use infer instead of Parameters<> + ReturnType<>?
You could write it using built-in utilities:
type AppendArgument<Fn extends (...args: any[]) => any, A> =
(...args: [...Parameters<Fn>, A]) => ReturnType<Fn>This works fine and is arguably more readable. The conditional infer version is more idiomatic for challenges but both are valid in practice.
Tuple spread in function signatures
(...args: [...Args, A]) relies on variadic tuple types introduced in TypeScript 4.0. Before TS 4.0, spreading a generic tuple into rest parameters wasn't supported — you'd have to use overloads or more complex workarounds.
The spread [...Args, A] is a tuple concatenation: it takes the original params tuple and appends one more element. This is distinct from array spread — TypeScript tracks the exact position and type of each element.
What if Fn has optional or rest parameters?
type Fn1 = (a?: number) => void
// Args = [number?]
// AppendArgument<Fn1, string> = (a?: number, x: string) => void
type Fn2 = (...args: number[]) => void
// Args = number[]
// AppendArgument<Fn2, string> = (...args: [...number[], string]) => voidThe infer approach handles these cases naturally — it captures whatever the original signature looks like.
never fallback
: neverIf Fn somehow doesn't match (...args: infer Args) => infer R (which can't happen given the constraint), we fall through to never. It's a defensive catch-all.
Key Takeaways
infer Argson a rest parameter captures all parameters as a tuple — perfect for manipulation[...Args, A]uses variadic tuple types to append a new type to an existing parameter tuple- You can always rewrite conditional
inferinference usingParameters<>andReturnType<>— pick whichever is clearer - Variadic tuple types (TS 4.0+) are the foundation for many advanced function-manipulation challenges
