Good Types Describe Decisions, Not Just Data
Types Begin as Questions About Real Usage
When you add a type to a function, you are really writing down a promise about what that function agrees to handle. Newcomers often treat types as decoration that keeps the compiler quiet, but the best ones come from asking honest questions about use. Who calls this code, and what can they legitimately send? What does success look like, and what can go wrong along the way?
Start with the values that actually reach the boundary. If a field is an identifier, it should not also silently accept an empty string. If a list can be empty, say so, because an empty list is a real state with real consequences. The goal is not to make every value impossible to construct; the goal is to make the illegal cases hard to express and the legal cases easy to read.
Plain-language names help here. A reader who knows the domain should recognise the shape of your data without opening the implementation. Good type design is a small piece of technical writing, and it succeeds when the next engineer understands the rule before they break it.
One practical habit is to write the type before the implementation and then describe it aloud in a single sentence. If that sentence needs the word and twice, the type is probably doing two jobs. Splitting it early is far cheaper than refactoring later, and it gives your reviewer a cleaner surface to question.
Model Reviewers Surface Ambiguity Faster Than Memory
Reading your own types is unreliable because you already know what you meant. A reviewer with fresh eyes catches the gaps you skim past. A language model works surprisingly well in that role, especially for small, focused questions such as whether a name is accurate or whether a shape allows contradictory states.
The workflow is simple. Share the type definitions with a short note about the domain, then ask for weaknesses, edge cases and misleading names. Ask for questions rather than answers when you want to think, because a good question exposes an assumption you had not written down. Models are strong at noticing asymmetries: fields required in one place and optional in another, unions that overlap, errors that share a shape but mean different things.
Where you run that review matters too, because you want the exact model you asked for rather than a silent substitute. BestRelay is a quality-first relay that forwards each request to the official model named in it, with no substitution, covering Claude, GPT, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok and more. A careful reasoning pass might name Claude Opus 5.5, while a quick naming check could use Gemini 3.8 Flash, and each request goes exactly where you said. The relay works with Claude Code, Codex, Gemini CLI and any OpenAI SDK through a single API base, so the same review conversation follows you across tools.
Narrow Types Trade Flexibility for Compile-Time Safety
Every type is a trade. A wide type is cheap to write and forgiving at call sites, but it pushes mistakes to runtime where users find them. A narrow type costs more thought up front and turns some errors into compiler messages that appear while you are still in the editor. For newcomers, the goal is a middle path: narrow enough to catch real mistakes, loose enough that ordinary work does not become a fight.
Prefer unions of literal values over free strings when the possibilities are known, and prefer separate types for separate meanings even when two things happen to share a shape today. Money and quantity are both numbers in memory, yet merging them invites bugs that no test may notice for months. If a value must stay broad, wrap it once at the edge where data enters, so the rest of the codebase can rely on a stricter promise.
Watch for over-engineering as well. Generics that serve a single caller, deep nested optionals and clever conditional types all raise the reading cost of a file. A type that needs a paragraph of explanation is usually answering the wrong question.
Naming Decisions Carry More Weight Than Annotations
The name of a type is the part everyone reads first, and it sets expectations before any annotation is parsed. Vague names such as data, item or response tell a reader nothing and slowly grow until they mean several unrelated things. Precise names such as retry budget or paginated result describe a decision the code has already made.
Keep names and responsibilities aligned. If a type is called a request, it should not also carry retry policy and telemetry. Splitting types by responsibility makes each one easier to review, and it keeps changes local when requirements move. Suffixes like config, result and options help only when they reflect a real difference in how the value is used.
Consistency across a codebase compounds. If one module calls the user identifier user ID and another calls it account reference, readers must hold both meanings in mind, and mistakes hide in the gap. Agreeing on a shared vocabulary is quiet work with visible results: fewer conversions, clearer diffs, and reviews that focus on behaviour instead of spelling.
A model reviewer is useful for naming as much as for shape. Ask whether a name would mislead someone who knows the domain but not the codebase, and ask for two alternatives with reasoning. You are not obliged to take either suggestion; you are looking for the moment when your own choice feels obvious again.
Iterate With a Reviewer Until the Types Settle
Good types usually arrive in rounds. Write a first version, explain the domain in one sentence, and ask for weaknesses. Fix the obvious ones, then ask again with the updated code. The second round matters because reviewers tend to describe symptoms first and causes later, and a small change often reveals a deeper mismatch that both of you missed.
Keep each round short and specific. Broad prompts produce generic advice, while a prompt that names one function and one concern produces comments you can act on immediately. When the reviewer starts repeating itself and the compiler is quiet, you are close to done, and the remaining decisions are taste rather than correctness.
Cost discipline keeps the habit sustainable. A relay that bills pay-as-you-go from the token counts the provider reports, with cache reads and writes priced separately, makes each review round predictable, and members earn a referral commission when the people they invite top up. The economics matter because type design is a recurring conversation, not a one-off task.
Create a BestRelay account when you want to try this workflow.