# Writing Idiomatic TypeScript with Clear Types

> Write idiomatic TypeScript by making type boundaries, narrowing, and intent clear without using assertions or generic abstractions to hide unresolved runtime questions.

- Canonical HTML: [https://unrust.dev/languages/typescript/idiomatic-code](https://unrust.dev/languages/typescript/idiomatic-code)
- Markdown representation: [https://unrust.dev/languages/typescript/idiomatic-code/index.md](https://unrust.dev/languages/typescript/idiomatic-code/index.md)

Published: 2026-07-14

## Direct 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.

## 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.

## Original example

### Keep an API result's alternatives visible

```typescript
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.

## 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

1. Replace one broad TypeScript object type with the smallest named shape that communicates the actual contract.
2. Review a cast and locate the validation or invariant that makes it safe to use.
3. Compare 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

Editorially reviewed for Unrust using public language documentation, common code-review practice, and small original examples written for this resource system.

## Related resources

- [How to Prepare for a Focused Code Review](https://unrust.dev/use-cases/prepare-for-code-review/index.md): Prepare a small, evidence-rich code review by clarifying the contract, reducing unrelated changes, and showing tests that cover the risky behavior.
- [How to Test an AI-Suggested Refactor](https://unrust.dev/use-cases/test-an-ai-suggested-refactor/index.md): Test an AI-suggested refactor by preserving observable behavior, specifying invariants, and comparing before-and-after cases instead of trusting cleaner-looking code.

Next step: [Take the free diagnostic](https://unrust.dev/diagnostic/index.md)
