TypeScript skill practice
How do you approach this?
Writing Idiomatic TypeScript with Clear Types
Published · Updated
Published by Unrust. Scoring and editorial method
Quick answer
Idiomatic TypeScript makes both intent and uncertainty visible: use named domain types, narrow values with evidence, model meaningful alternatives explicitly, and keep runtime validation near external boundaries. The shortest generic or assertion-heavy version is not automatically the most idiomatic. Prefer a form that lets a teammate see what inputs are trusted, what failures are possible, and how the surrounding code handles them.
Original example
Keep an API result's alternatives visible
type LoadResult =
| { ok: true; user: { id: string } }
| { ok: false; reason: "not-found" | "forbidden" };
function message(result: LoadResult) {
return result.ok ? result.user.id : result.reason;
}This original TypeScript example uses a discriminated union because success and failure lead to different caller decisions. It would be less helpful if the application needed a generic error framework or more metadata, so idiomatic code still depends on the local contract. The key review question is whether the type makes the actual alternatives easier to test and handle.
Model meaningful alternatives deliberately
Discriminated unions, optional fields, and narrow return types can make a domain rule easier to inspect when they represent real alternatives. Avoid inventing elaborate type machinery for a simple local value, but do not collapse success, absence, and failure into a vague shape that forces callers to guess.
Use assertions sparingly and explain their evidence
An assertion can document a trusted invariant after a parser or guard, but it should not become the default response to a compiler complaint. If a teammate cannot find the evidence behind a cast, the code is carrying an invisible runtime assumption that deserves validation or a clearer interface.
Check a concrete outcome
Use the original example with this input. These are local teaching fixtures, checked against the displayed snippet; they do not call external services.
Both union variants have a checked outcome; no assertion is needed to access the success payload.
| Expression after the example | Observed result |
|---|---|
| [message({ok: true, user: {id: "u1"}}), message({ok: false, reason: "forbidden"})] | ["u1", "forbidden"] |
Checklist
- Does this TypeScript type represent a real domain alternative or only silence uncertainty?
- Can a caller see the success and failure paths without a cast?
- Where is external data validated before entering this typed boundary?
Practice loop
- 01Replace one broad TypeScript object type with the smallest named shape that communicates the actual contract.
- 02Review a cast and locate the validation or invariant that makes it safe to use.
- 03Compare a generic abstraction with a direct branch for clarity at the calling boundary.
Limits of this page
TypeScript idioms vary with compiler settings, framework conventions, generated clients, and team preferences. A short style exercise can focus a review conversation, but it cannot rank developers or determine why a local pattern did not immediately feel familiar.
Source context
Published by Unrust as practical teaching material using public language documentation and original examples. See the scoring and editorial method for scope, checking and limitations.
Check your own starting point
One diagnostic session is a snapshot, not causal proof of why a result occurred. It can help you choose a focused next practice step without making a broader claim about your ability.
Take the free diagnosticTake the read in TypeScript →Same six skills, side by side on your dashboard.