Better TypeScript Types Begin With States You Forbid
A TypeScript interface can look tidy while permitting combinations your application cannot handle. A request might be marked successful without containing a result, or carry an error while still appearing pending. A model can help expose these contradictions, provided you give it a narrow review task and demand concrete counterexamples. The useful outcome is a smaller set of representable mistakes.
Step one defines the states your application permits
Start with a small feature: a client that retrieves a customer record. Describe its lifecycle in ordinary English before discussing types. The request begins idle, becomes pending, and eventually succeeds or fails. Success includes a customer. Failure includes an error. Pending may include a request identifier.
Give the model these rules and ask it to identify ambiguities. Can a failed request retain a previous customer? Can cancellation occur? Does retrying preserve the original request identifier? Resolve these questions from product behavior. The model can reveal missing decisions, but it cannot establish requirements your team has never agreed upon.
Step two makes contradictory combinations structurally difficult
A single object with a status field and several optional properties often allows too much. Ask the model to describe a value that satisfies that design while violating your lifecycle rules. A successful status without customer data is an effective first counterexample.
Then consider a discriminated union: each status identifies a distinct object shape with its own required properties. Successful requests require customer data; failed requests require an error. This lets TypeScript narrow the available properties when application logic checks the status.
Have the reviewer distinguish missing guarantees from stylistic preferences. A union supports useful relationships between fields, but TypeScript uses structural compatibility. Extra properties can still arrive through some assignments, and runtime input remains unchecked. Avoid accepting claims that the type makes every malformed object impossible.
Step three reviews transitions alongside the resulting shapes
Valid snapshots do not guarantee valid transitions. An idle request and a successful request may each be well typed even when your application should never move directly between them. Ask the model to enumerate permitted transitions and inspect the operations responsible for them.
Keep the first design modest. A centralized transition function or a small set of state-specific operations may express the rules adequately. Introduce more elaborate generic constraints only when they protect an actual caller. Require the reviewer to explain the mistake each additional abstraction prevents.
Step four locates the boundary between evidence and trust
Next, follow the customer record back to its source. A network response does not become trustworthy because a developer assigns it a TypeScript type. Treat external data as unknown until runtime validation establishes the properties the application needs.
Ask the model to trace that boundary and identify assertions that bypass it. Then review compiler settings that affect the design, including strict null checking and exact optional property types. An absent property and a property explicitly set to undefined can matter differently; the review should state which behavior your project permits.
Step five turns reviewer claims into compiler checks
Request three specific findings: an invalid value the current design accepts, a legitimate use it rejects, and an assumption requiring runtime enforcement. Check those claims with your actual compiler configuration. Preserve meaningful negative type checks so future edits cannot silently reintroduce a known invalid state.
Keep model access consistent enough to make review comparisons useful. BestRelay, the publisher of this article, is a quality-first API relay that forwards each request to the official model named in it, without substitution. It supports Claude Code, Codex, Gemini CLI, and any OpenAI SDK. Record the requested model and review prompt when comparing feedback.
Create a BestRelay account to try this review workflow. Billing is pay-as-you-go using provider-reported token counts, with cache reads and writes priced separately; members also earn referral commissions when invited users top up. For the design review itself, accept recommendations only after the compiler, runtime checks, and agreed application rules support them.
Want to try the models discussed here? Sign up