| name | typescript-best-practices |
| description | Opinionated TypeScript conventions for inference-first typing, precise contracts, finite-state modelling, exhaustive decisions, and strict handling of optional or untrusted values. Use when writing or reviewing TypeScript types, state transitions, API signatures, schema-adjacent parsing, array or regular-expression access, or strict compiler errors. |
| license | MPL-2.0 |
| metadata | {"author":"Basti Ortiz <ortiz@bastidood.dev>","source":"https://github.com/BastiDood/skills"} |
TypeScript Best Practices
Treat types as precise, compiler-checked models of valid program states and boundaries; preserve inference where it carries information, then narrow uncertainty explicitly so invalid states and unhandled cases cannot silently reach runtime.
References
Read as many linked references as are relevant to the current task before writing or reviewing TypeScript.
- When TypeScript can derive the correct type, keep the annotation inferred because repeated annotations drift and can hide errors.
- At an untrusted-input or caught-error boundary, contain type escape hatches by narrowing or validating
unknown immediately instead of propagating it into domain code.
- When an API infers a closed state from one initial literal, declare the intended state set at that inference boundary so later valid members remain assignable.
- When a durable domain concept has a named closed set of serialized primitive values, model it as a
const enum rather than a string-literal union.
- When writing an object contract, choose
interface by default and compose it with extends; reserve type for contracts that interfaces cannot express naturally.
- When a value is untrusted or insufficiently typed, replace unchecked assertions with runtime proof at its serialization boundary.
- When consumers require readonly tuple or literal-union inference from a literal constant, preserve it with
as const rather than treating the assertion as a default.
- When a value must satisfy a shape without losing its precise inferred literals, use structural conformance instead of widening it with an annotation.
- When parallel flags permit impossible state combinations, model the state as discriminated variants so each status has one valid shape.
- When each member of a closed enum or union needs distinct behavior, make the decision exhaustive so new members cannot silently reach a fallback.
- When an operation requires a value that might be absent, establish that invariant explicitly rather than asserting it with
!.
- When an optional argument can be omitted, express omission by leaving it out; pass
void 0 only when the API requires a positional value.
- When an operation requires a present optional value, narrow it before the call, or preserve absence in its return or state contract when it is expected.
- When a required input is missing, do not fabricate a default unless the domain contract assigns that exact omitted-value meaning.
- When bracket access or destructuring can produce no array element, guard the indexed value before treating it as present.
- When a
.split() component can be absent, preserve that absence until domain or display policy resolves it instead of supplying an arbitrary empty string.
- When a regular-expression capture supplies a required domain field, validate the capture first because named and positional groups can be absent.