`any` opts a value out of type checking entirely, and that opt-out is contagious — every expression derived from it is also unchecked. `unknown` is the safe top type: it holds any value but permits no operation until you narrow it.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add snoodleboot-io/prompticorn --skill verbose --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Verbose?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/snoodleboot-io-verbose-5d55d9d7)More formats (shields.io, HTML) on the badges page.
# TypeScript Type System (Verbose)
## Core Patterns
### unknown Over any
`any` opts a value out of type checking entirely, and that opt-out is contagious —
every expression derived from it is also unchecked. `unknown` is the safe top
type: it holds any value but permits no operation until you narrow it.
```typescript
function handler(raw: unknown): number {
if (typeof raw === "number") return raw; // narrowed to number
if (typeof raw === "string") return raw.length; // narrowed to string
throw new TypeError("unsupported");
}
```
Reserve `any` for genuine escape hatches (untyped third-party code) and isolate
it behind a typed wrapper so it cannot leak downstream.
### Narrowing And Type Guards
The compiler narrows a union as you test it — `typeof`, `instanceof`, `in`,
equality, and truthiness all narrow. For reusable checks, write a user-defined
type guard whose return type is a type predicate.
```typescript
interface Cat { meow(): void; }
interface Dog { bark(): void; }
function isCat(a: Cat | Dog): a is Cat {
return "meow" in a;
}
function speak(a: Cat | Dog): void {
if (isCat(a)) a.meow(); // a is Cat here
else a.bark(); // a is Dog here
}
```
### Discriminated Unions And Exhaustiveness
A shared literal field lets the compiler pick the exact variant per branch. Add a
`never` default to force a compile error when a new variant is added later.
```typescript
type Event =
| { type: "click"; x: number; y: number }
| { type: "key"; code: string };
function handle(e: Event): void {
switch (e.type) {
case "click": return move(e.x, e.y);
case "key": return press(e.code);
default:
const _exhaustive: never = e; // compile error if a variant is unhandled
return _exhaustive;
}
}
```
### Generics With Constraints And Inference
Generics carry the caller's type through the function; `extends` restricts what a
parameter accepts.
```typescript
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((it) => it[key]);
}
const ages = pluck(users, "age"); // number[], inferred — no annotation needed
```
Inference has limits: TypeScript infers from arguments, not from the expected
return type flowing backward through a generic chain. When inference fails, supply
the type argument explicitly rather than fighting it.
### satisfies
`satisfies` checks a value against a type while preserving the narrower inferred
type — you get validation without widening.
```typescript
const palette = {
primary: "#0a7",
danger: "#e33",
} satisfies Record<string, `#${string}`>;
palette.primary.toUpperCase(); // still known to be a string literal, and checked
```
Without `satisfies`, annotating `: Record<string, string>` would widen every value
to `string` and lose the literal keys.
### Structural Typing
TypeScript types are compatible when their shapes are compatible, regardless of
name. A value with extra properties still satisfies a narrower type — except for
fresh object literals, where excess-property checks flag likely typos.
```typescript
interface Named { name: string; }
const full = { name: "Ada", age: 36 };
const n: Named = full; // ok — structural
const bad: Named = { name: "Ada", agee: 36 }; // error — excess property on literal
```
### Utility And Mapped Types
Derive types instead of duplicating them. Built-ins cover the common cases:
| Utility | Produces |
|---|---|
| `Partial<T>` | all properties optional |
| `Required<T>` | all properties required |
| `Pick<T, K>` | subset of keys |
| `Omit<T, K>` | all keys except K |
| `Readonly<T>` | immutable view |
| `Record<K, V>` | object type from keys to values |
| `ReturnType<F>` | a function's return type |
A mapped type expresses your own transformations:
```typescript
type Nullable<T> = { [K in keyof T]: T[K] | null };
```
## Common Anti-Patterns
❌ **any as a shortcut**
```typescript
function parse(json: string): any { return JSON.parse(json); }
```
✅ **unknown plus narrowing**
```typescript
function parse(json: string): unknown { return JSON.parse(json); }
```
❌ **Assertion to force a mismatch through**
```typescript
const user = data as User; // no check — a claim the compiler now trusts blindly
```
✅ **Validate, then the type is earned**
```typescript
if (isUser(data)) { /* data is User here */ }
```
❌ **Optional-bag object where a union belongs**
```typescript
interface Resp { ok?: boolean; data?: string; error?: string; }
```
✅ **Discriminated union — illegal combinations become unrepresentable**
```typescript
type Resp = { ok: true; data: string } | { ok: false; error: string };
```
❌ **Type puzzle nobody can maintain**
```typescript
type DeepKeys<T> = /* five nested conditional + mapped types */ never;
```
✅ **Prefer the simplest type that holds the invariant.** Cleverness costs the
next reader; spend it only where it prevents real bugs.
## Type System Checklist
- [ ] `unknown` (not `any`) at untyped boundaries, narrowed before use
- [ ] `strict` enabled in `tsconfig.json`
- [ ] Unions discriminated by a literal tag, with a `never` exhaustiveness guard
- [ ] Type guards used for reusable runtime narrowing
- [ ] Generics constrained with `extends`; explicit args only where inference fails
- [ ] `satisfies` used to validate without widening literals
- [ ] Types derived via utility/mapped types instead of hand-duplicated
- [ ] `as` assertions justified and rare; no blind `@ts-ignore`
- [ ] Type complexity kept to what the invariant actually needs
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!