In TypeScript, you are wrapping an existing `sendEmail(to: string, subject: string, body: string): boolean` with a logging version. How do you type the wrapper so its parameters stay in sync with `sendEmail`, and what exactly is the type `Parameters<typeof sendEmail>`?
answer
- the result is a tuple, not an object
- tuple belongs in a rest parameter
- labels and optionality survive
- index it for one position
- spread forwards it positionally
basics
~10 sDeclare the wrapper as (...args: Parameters<typeof sendEmail>) and spread args into the call. Parameters<typeof sendEmail> is the labelled tuple [to: string, subject: string, body: string], so adding or reordering parameters updates the wrapper automatically.
solid answer
~40 sI'd write `function logged(...args: Parameters<typeof sendEmail>): ReturnType<typeof sendEmail>` and forward with `return sendEmail(...args)`. `Parameters<T>` produces a **tuple** of the signature's parameter types — here `[to: string, subject: string, body: string]` — keeping the parameter names as tuple labels and preserving optional and rest elements, so `(name: string, times?: number)` becomes `[name: string, times?: number]`. Because it is a tuple, it belongs in a rest parameter, not a single parameter: `(args: Parameters<...>)` would take one array argument instead of three. You can also index it to grab one position, `Parameters<typeof sendEmail>[0]`, which is handy for naming a single argument type. When the signature changes, the wrapper and every call site move with it.
code
typescript · 18 linesfunction sendEmail(to: string, subject: string, body: string): boolean {
return to.length + subject.length + body.length > 0;
}
// The tuple goes into a REST parameter, then spreads back out.
function loggedSendEmail(
...args: Parameters<typeof sendEmail>
): ReturnType<typeof sendEmail> {
console.log("sending to", args[0]);
return sendEmail(...args);
}
// Index the tuple to name a single argument's type.
type Recipient = Parameters<typeof sendEmail>[0]; // string
const ok: boolean = loggedSendEmail("[email protected]", "hi", "there");
const who: Recipient = "[email protected]";
console.log(ok, who);go deeper
Recognise the (...args: Parameters<typeof fn>) pattern when you see it, and know it means "same parameters as that function" rather than a single array argument.
Explain that the result is a labelled tuple preserving optional and rest elements, that it must sit in a rest parameter, and that indexing it names one argument's type.
Judge when a wrapper should inherit the wrapped signature at all — transparent decorators yes, public boundaries usually no — and know that overloaded or generic targets need a different approach.
Decide how far derivation should spread through a codebase's wrapper layers, weighing the refactor leverage against wrappers that silently inherit a bad contract they should have narrowed.
## The problem Wrapping a function — logging, retrying, timing, feature-flagging — means repeating its parameter list. Repeat it by hand and the copy rots the first time someone adds a parameter to the original. `Parameters<T>` removes the copy. ## What `Parameters` produces It is a one-line conditional type from TypeScript's library: ```ts type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never; ``` The key fact is the **shape of the result**: a tuple type, not a union and not an object. ```ts declare function greet(name: string, times?: number): void; type G = Parameters<typeof greet>; // [name: string, times?: number | undefined] ``` Three things travel with it. Parameter **names** are kept as tuple element labels, which is why tooltips and error messages stay readable. **Optionality** is preserved — the `?` survives into the tuple element. And a **rest parameter** stays a rest element, so `(...ids: number[])` yields `[...ids: number[]]`. Note the `typeof`: `Parameters` is constrained to a function type, so it needs a type query over the function value, not the identifier. ## Using it in the wrapper ```ts function sendEmail(to: string, subject: string, body: string): boolean { return to.length > 0; } function loggedSendEmail( ...args: Parameters<typeof sendEmail> ): ReturnType<typeof sendEmail> { console.log("sending to", args[0]); return sendEmail(...args); } loggedSendEmail("[email protected]", "hi", "there"); // three arguments, fully checked ``` The tuple lands in a **rest parameter**. That is the whole trick, and it is where candidates slip: writing `(args: Parameters<typeof sendEmail>)` declares a function of one argument that happens to be a three-element array, so callers would have to write `logged(["[email protected]", "hi", "there"])`. Both compile; only one has the signature you wanted. Spreading a tuple into a call is checked positionally — `sendEmail(...args)` type-checks element by element, so a mismatch is caught rather than degraded to `any`. ## Indexing a single position Because the result is a tuple, ordinary indexed access works: ```ts type Recipient = Parameters<typeof sendEmail>[0]; // string type Subject = Parameters<typeof sendEmail>[1]; // string ``` This is the idiomatic way to name "the type of the second argument of that function" without re-declaring it. It is most valuable when the parameter type is itself a large object — an options bag, a config record — that has no exported name of its own. ## What it does *not* solve It is worth being honest about the ceiling here. `Parameters` reports the signature the checker resolves, so an overloaded function yields only one of its signatures, and a generic function comes back with its type parameters already instantiated rather than left open for your wrapper to relate. For a wrapper over a simple, single-signature function it is exactly right; for the awkward cases the wrapper usually needs to be generic itself. There is also a design question hiding in the idiom. Deriving the wrapper's parameters means the wrapper has *no independent contract* — it inherits whatever the wrapped function declares, including a bad parameter list. That is the point when the wrapper is genuinely transparent (logging, timing), and a liability when the wrapper is a public boundary that should hold its own shape. ## Runtime cost None. `Parameters<typeof sendEmail>` is erased; the emitted wrapper is a plain rest-and-spread function. The tuple exists only to make the compiler check the forwarding for you.
- What breaks if you write `(args: Parameters<typeof sendEmail>)` without the rest dots?It compiles, but you have declared a one-parameter function whose argument is a three-element tuple. Callers must pass an array literal, `logged(["[email protected]", "hi", "there"])`, and every existing call site breaks. The rest parameter is what spreads the tuple across positions, turning `[to: string, subject: string, body: string]` back into three separate arguments.
- How would you name just the type of the second parameter without re-declaring it?Index the tuple: `type Subject = Parameters<typeof sendEmail>[1]`. Because `Parameters` returns a tuple type, ordinary indexed access selects one position. This is especially useful when a parameter is a large options object that the module never exported under its own name.
- Does `Parameters` keep optional parameters optional?Yes. `(name: string, times?: number)` produces `[name: string, times?: number | undefined]`, an optional tuple element. Spread through a rest parameter, the wrapper accepts one or two arguments exactly as the original did. Rest parameters survive too, arriving as a rest element in the tuple.
saying these in an interview costs you the question
- Thinks Parameters returns an object keyed by parameter name
- Omits the rest dots and changes the wrapper's arity
- Believes Parameters loses optional and rest markers
- Uses Parameters<sendEmail> without the typeof query
- Assumes the tuple must be re-typed manually to be spread