Course outline

TypeScript / Intermediate / Lesson 7

Generics and key relationships

Preserve useful relationships across calls instead of replacing them with any, and constrain generic operations to capabilities actually required.

About 5 min readContent reviewed
On this page

Lesson goals

  • Explain type parameters, inference, and constraints.
  • Relate keys to their corresponding property value types.
  • Avoid generic signatures that promise runtime construction they cannot perform.

A type parameter connects uses of the same type

A generic function can express a relationship between input and output without fixing one concrete type. That is more useful than an unconstrained placeholder appearing only once. Using any loses relationships by permitting unchecked operations and allowing the result to flow into unrelated types. unknown is safer at an untrusted boundary because operations require evidence, but it does not by itself preserve a caller-specific result relationship. Choose a type parameter when the operation genuinely works for multiple types under the same contract. Type parameters are erased and do not create runtime constructors or schemas for those types.

Relate an accepted key to its exact result

keyof produces a union of known property keys, and indexed access T[K] describes the value type for a selected key. The first generic constrains K to keys of T, so a checked caller cannot request an unrelated spelling. The result remains number for id and string for label. The implementation performs ordinary JavaScript indexing; it does not validate an external object or manufacture a missing field. Optional properties can still produce undefined, and broad index signatures have different guarantees from a finite object with known keys. Review the actual T inferred at the call.

typescript A key controls the returned property type
export {};
function get<T, K extends keyof T>(object: T, key: K): T[K] { return object[key]; }
const item = { id: 7, label: "map" };
console.log(get(item, "id"), get(item, "label"));
Expected output
7 map

The two checked keys select their existing fields. The helper retains each property type instead of returning any.

Let the callback determine the output element type

The second function uses separate input and output parameters because transformation can change element type. Inference obtains both from the array and callback without explicit type arguments at the call site. The readonly input communicates that the function does not need to mutate through this view. Its implementation delegates to map and returns a newly created array. No Clone or object constraint is necessary because the function does not inspect input members. Extra constraints can reject valid callers and make generic inference harder without making the implementation safer.

typescript Infer an element transformation relationship
export {};
function convert<T, U>(values: readonly T[], operation: (value: T) => U): U[] {
  return values.map(operation);
}
const labels = convert([1, 2], value => `n${value}`);
console.log(JSON.stringify(labels));
Expected output
["n1","n2"]

T is number and U is string. The resulting array contains strings in the original traversal order.

A constraint is a minimum capability, not a concrete value

T extends { length: number } permits reading length but does not let the implementation return any arbitrary object with a length as T. A caller may instantiate T with additional required properties, literal restrictions, or a more specific class. Constructing a generic T therefore needs an actual factory, input value, or another justified relationship. Do not assert a convenient object to T to silence that design defect. Prefer returning the real result shape or asking the caller for a construction callback. Generics should make valid relationships visible, rather than hiding unknown assumptions inside the function body.

Test inference from a consumer perspective

A useful generic API should infer ordinary calls clearly and report incorrect relationships near the call. Test empty arrays, literal values, optional keys, readonly inputs, and callback results with different types. Explicit type arguments are appropriate when they convey a deliberate choice, but should not be required merely to work around a needlessly complex signature. Generic types do not validate JSON.parse results, prove numeric ranges, or authorize a requested property. For external key selection, validate the key and access policy before calling a helper that assumes its inputs already meet the contract.

Common errors and fixes

A generic helper returns any and loses property precision.

Why it happens
The result is disconnected from the input key.
How to fix it
Express K extends keyof T and T[K] where appropriate.
Prevent it next time
Review relationships rather than adding type parameters mechanically.

A constrained generic cannot return a fabricated T.

Why it happens
The constraint omits more specific caller requirements.
How to fix it
Return the real shape or request a construction factory.
Prevent it next time
Never use assertions as generic construction.

Extra bounds reject useful arrays.

Why it happens
The signature requires capabilities the body never uses.
How to fix it
Remove unnecessary object or member constraints.
Prevent it next time
Derive each bound from an actual operation.

Check your understanding

1. Add an optional description?: string to the item model. What should get return for that key, and how should a caller format absence?

Show worked solution

The return type includes string | undefined because the property may be absent. A caller can use ?? with a documented fallback or branch on undefined. The generic relationship preserves uncertainty rather than turning an optional field into a guaranteed string. Validate external content separately before constructing the item type.

2. Use convert to map [2, 4] to objects containing a doubled field. State the inferred output shape and exact JSON result.

Show worked solution

Return { doubled: value * 2 } from the callback. U is inferred as an object with a numeric doubled property, and the JSON result is [{"doubled":4},{"doubled":8}]. No assertion or explicit U argument is needed because the callback supplies concrete evidence. The arithmetic still assumes accepted numeric inputs and an appropriate domain.

Key takeaways

  • Use generics to preserve actual relationships.
  • A constraint permits operations, not arbitrary construction.
  • Test inferred consumer calls and external boundaries separately.

Official references