Type-first TypeScript coding practices — designing types before implementation, making illegal states unrepresentable with discriminated unions, runtime validation with Zod at system boundaries, and module organization (single-function modules, barrel files, encapsulation). Use when starting a new feature by asking what the types should look like first, spotting an interface with too many optional fields that let impossible combinations compile, or deciding how to validate data coming from outside the type system (an API response, form input, environment variables).
71
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Workflow-level habits: design types first, make bad states impossible to construct, validate at the boundary where untyped data enters, and organize modules so the type-first discipline stays easy to keep up.
Design the types before writing the implementation — a type is a specification the compiler checks on every subsequent change, and writing it first forces the impossible states question to be answered up front rather than discovered later as a bug. An interface with many optional fields is usually a discriminated union that hasn't been modeled yet: if data and error can theoretically both be set, that combination will eventually occur, and the fix is a type that excludes it, not a runtime check that catches it after the fact. Runtime validation belongs at the boundary — the one place untyped data (an API response, form input, an environment variable) enters the typed world — not scattered through the code that consumes it; validate once there, and let the type system carry the guarantee everywhere downstream.
Use this skill when:
typescript-type-guards skill for hand-written predicates.typescript-type-system skill; this skill covers the practice of reaching for one, not how unions narrow.typescript-compiler-config skill.Do not use this skill to look up narrowing syntax or how a discriminated union's exhaustiveness check works mechanically — see typescript-type-guards for that. Do not use it as a substitute for tsc --noEmit — a type-first design still needs the compiler to confirm every consumer respects it.
npx tsc --noEmitnpm install zodWHY: independent optional fields let combinations compile that should be impossible, so the invalid combination becomes a runtime bug instead of a compile error.
BAD:
interface FetchState<T> {
isLoading: boolean;
data?: T;
error?: Error; // data and error can both be set — impossible in practice, allowed by the type
}GOOD:
type FetchState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "done"; data: T }
| { status: "done"; error: Error };WHY: a type annotation on fetch().json() is a claim, not a check — the actual runtime value could be anything, and nothing enforces the annotation matches reality.
BAD:
async function getUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json(); // asserted to be User, never verified
}GOOD:
const UserSchema = z.object({ id: z.string(), name: z.string() });
async function getUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return UserSchema.parse(await res.json());
}WHY: exporting internals a module wasn't designed to expose removes the compiler's ability to catch a caller depending on something that was meant to change freely.
BAD:
// db.ts
export let connection: Connection; // internal state exported directly
export function connect() { connection = createConnection(); }GOOD:
// db.ts
let connection: Connection;
export function connect() { connection = createConnection(); }
export function getConnection(): Connection {
if (!connection) throw new Error("Not connected");
return connection;
}| File | Covers |
|---|---|
references/type-first.md | Type-first development workflow |
references/illegal-states.md | Making illegal states unrepresentable with discriminated unions |
references/runtime-validation.md | Zod-based validation at system boundaries |
references/module-patterns.md | Single-function modules, barrel exports, encapsulation |
a1083f4
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.