Typorithm · easy

7. Function contracts, callbacks and overload restraint

typescriptstable-typescriptinterviewindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

7. Function contracts, callbacks and overload restraint

0%

Concept and contract

Describe inputs, outputs and callback behavior without weakening the API to any.

A function type states which calls a consumer may make and what result the implementation promises. Parameter names improve readability but do not establish nominal identity. Optional and defaulted parameters allow omission; rest parameters describe a variable-length sequence with an explicit element or tuple type.

Use a union parameter when one implementation handles a small set of alternatives with the same output type. Reach for overloads when distinct input shapes genuinely imply distinct output types. Extra signatures add a contract to maintain, not an automatic improvement in safety.

Mechanism and worked example

The example accepts a readonly collection and a callback that maps each title to another string.

The example accepts a readonly collection and a callback that maps each title to another string. The callback receives a string because its expected type supplies context. The implementation may invoke it for every element, so callers must not assume it receives only one special literal.

A void callback result means the caller will ignore the return value, not that the supplied function must literally return undefined. A callback returning a number can satisfy a void-consuming API. By contrast, a declared function returning never cannot return normally.

Complete worked example

typescript

function labels(values: readonly string[], transform: (value: string) => string): string[] {
  return values.map(transform);
}
const names = labels(["qa", "pm"], value => value.toUpperCase());
Reasoning
Context supplies the callback parameter type; the returned array is newly allocated.

Counterexamples and common mistakes

The rejected callback accepts a number, but the implementation will supply a string.

The rejected callback accepts a number, but the implementation will supply a string. Changing the callback parameter to any would conceal the mismatch. Under strictFunctionTypes, ordinary function-property parameters receive stricter compatibility checks; the advanced course explains method bivariance exceptions.

Avoid optional callback parameters merely because a caller may ignore an argument. A callback can accept fewer parameters than supplied; marking an argument optional instead tells callback authors that the implementation may call without it. Those are different promises and affect safe access.

Rejected counterexample: expected compiler diagnostic

typescript

function labels(values: readonly string[], transform: (value: string) => string): string[] { return values.map(transform); }
// @ts-expect-error The implementation supplies strings.
labels(["qa"], (value: number) => String(value));
Reasoning
A callback cannot require a different input type from the one the implementation supplies.

Independent practice

Implement mapLabels for readonly numeric values and a callback accepting a number and its index.

Implement mapLabels for readonly numeric values and a callback accepting a number and its index. Return a new string array. A caller should be free to ignore the index, while the implementation must always supply a real numeric index.

Test empty input, two values and a callback that uses both arguments. Add a rejected callback expecting a string in its first position. Do not mutate the input, and do not use overloads when one generic-free callback signature describes the complete contract.

Step 1: Write the callback contract with value and index. Step 2: Delegate each ordered element to the callback.

Practice starter: solve before reading the solution

typescript

function mapLabels(values: readonly number[], format: (value: number, index: number) => string): string[] { throw new Error("TODO"); }
Reasoning
The index is always supplied, even when a particular callback ignores it.
  • Hint 1: Ignoring an argument does not make that argument optional.
  • Hint 2: The input is readonly, so return a new array.

Solution, tests and trade-offs

The solution delegates to Array.map and keeps the callback contract explicit.

The solution delegates to Array.map and keeps the callback contract explicit. A callback using only value is assignable because it can ignore extra supplied arguments. A callback requiring a string is invalid because every invocation would violate that requirement.

Inspect both the output and the unchanged input. Compiler rejection and runtime behavior cover separate properties: the callback parameter type is checked statically, while output ordering and nonmutation require an execution test or an implementation argument.

Practice solution and acceptance checks

typescript

function mapLabels(values: readonly number[], format: (value: number, index: number) => string): string[] {
  return values.map((value, index) => format(value, index));
}

// Acceptance checks
const input=[4,9];if(mapLabels(input,(x,i)=>`${i}:${x}`).join(",")!=="0:4,1:9"||mapLabels([],String).length||input.join(",")!=="4,9")throw new Error("map");
Reasoning
Array.map preserves order and supports a callback with fewer used parameters.

Interview reasoning and next step

In an interview, explain void versus undefined versus never with observable behavior.

In an interview, explain void versus undefined versus never with observable behavior. Show a callback that returns a value to a void consumer and explain why ignoring that result is safe. Then describe an optional parameter’s promise from the callback author’s perspective.

Continue with arrays and tuples to express positional contracts. A function that needs a fixed pair deserves a tuple rather than a loose array whose second element might not exist.

Optional stretch — after the core task works: Make a generic mapping helper only after the concrete version works.

Describe inputs, outputs and callback behavior without weakening the API to any.