TypeScript
This skill extends the JavaScript skill. You must load javascript first — naming,
ternary operator rules, async patterns, and module conventions are defined there and
not duplicated here.
Types encode intent. Let the compiler prove the rest.
TypeScript's value is in catching bugs at compile time. Write types that express your domain; let inference handle the
obvious. Never fight the type system — if you need as or any, the types are wrong.
References
| Topic |
Reference |
Contents |
| Generics, utility types, type-level programming |
[${CLAUDE_SKILL_DIR}/references/generics.md] |
Utility type tables, conditional/mapped type examples, infer, template literals |
| Narrowing, type guards, discriminated unions |
[${CLAUDE_SKILL_DIR}/references/narrowing.md] |
typeof/instanceof/in examples, exhaustive switch, type predicates, assertion fns |
| tsconfig options, module resolution, project setup |
[${CLAUDE_SKILL_DIR}/references/configuration.md] |
Base/strict/module configs, library setup, compiler directives, project structure |
| Branded types, overloads, class patterns, enums |
[${CLAUDE_SKILL_DIR}/references/patterns.md] |
Interface vs type examples, assertion patterns, enum anti-patterns, callback types |
Type Safety
strict: true always. No exceptions. It enables strictNullChecks, noImplicitAny, strictFunctionTypes, and
other critical checks.
unknown over any. Use unknown and narrow with type guards. any disables type checking entirely. Reserve
any for migration or test mocks only — document why.
- No non-null assertions (
!) without justification. Prefer narrowing. If ! is truly needed, add a comment
explaining why the value cannot be null.
- No type assertions (
as) for object literals. Use type annotations (: Foo) instead — assertions hide
missing/extra property errors.
unknown vs any Decision
- Value from external source (API, JSON.parse, user input) →
unknown
- Function accepts anything, passes through without touching →
unknown
- Migrating JS to TS incrementally →
any (temporary, with comment)
- Test mock that intentionally bypasses type checking →
any (with comment)
The {} Type
{} means "any non-nullish value" — almost never what you want.
unknown → Everything (null, undefined, primitives, objects)
object → Non-primitive, non-null values
{} → Any non-nullish value (primitives included)
Record<string, unknown> → Objects with string keys
Prefer unknown for opaque values, Record<string, unknown> for dict-like objects, object when you need "any
non-primitive".
Type Annotations
- Omit trivially inferred types. Don't annotate
const x: number = 5 or const s: string = "hello". The compiler
infers these correctly.
- Annotate complex return types. When inference produces opaque or wide types, annotate explicitly for readability.
- Annotate function signatures at API boundaries. Exported functions and public methods should have explicit
parameter and return types.
- Use
import type for type-only imports. Enforced by verbatimModuleSyntax. Use export type for type re-exports
— required for isolatedModules.
- Annotate for precision with structural types. Annotate at declaration so errors appear where the bug is, not at
distant call sites.
Interfaces and Types
interface for object shapes. Better error messages, IDE support, and performance.
type for everything else — unions, intersections, tuples, function types, mapped/conditional types.
- Decision rule: Object shape with known properties?
interface. Everything else? type. Pick one pattern per kind
and stay consistent within a project.
- No empty interfaces. Use a branded type or discriminated union as a marker.
- No
namespace. Use ES modules. namespace is legacy.
- No wrapper types.
string not String, number not Number.
- Use interfaces for data shapes, not classes. A class used purely as a data shape adds unnecessary overhead.
Null Handling
- Prefer optional
? over | undefined for fields and parameters. | undefined forces callers to pass undefined
explicitly.
- Don't include
null/undefined in type aliases. Keep nullability at the use site:
function getUser(): User | null not type MaybeUser = User | null.
- Null narrowing:
!= null checks both null and undefined (the one valid == use). ?. for optional access. ??
for defaults.
Generics
- Name type parameters descriptively when meaning is non-obvious.
T is fine for single-parameter generics; use
TKey, TValue, TItem for multiple parameters.
- Constrain generics with
extends when possible. <T extends string> is better than <T> if T must be a
string.
- Keep generic constraints tight —
<T extends Record<string, unknown>> is better than <T extends object> when
you need string keys.
- Don't add unused type parameters. Every generic must appear in the signature.
- Avoid return-type-only generics. If a generic appears only in the return type, it cannot be inferred and forces
callers to guess.
- Let inference work. Don't specify type arguments when the compiler can infer them:
identity("hello") not
identity<string>("hello").
- Use type parameters in constraints:
<T, K extends keyof T> to relate parameters.
- Generic parameter defaults:
interface Container<T, U = T[]> — omitted type args fall back to the default.
NoInfer<T> (TS 5.4+) prevents a parameter from being an inference site — use when a parameter should be
constrained by other params, not drive inference.
Utility Types
Prefer built-in utility types over hand-rolling equivalents. Key types: Partial, Pick, Omit, Record, Exclude,
Extract, ReturnType, Parameters, Awaited, NoInfer. Use explicit interfaces when the type represents a distinct
domain concept. See ${CLAUDE_SKILL_DIR}/references/generics.md for the full catalog and usage guidance.
Conditional types (T extends U ? X : Y), mapped types ({ [P in keyof T]: ... }), and template literal types
(`${T}Changed`) are advanced tools — use for library code and framework types. See
${CLAUDE_SKILL_DIR}/references/generics.md for distributive behavior, infer, modifier removal, and key remapping.
Complexity Budget
- Simple (
interface, type alias, union) → Always — default choice
- Moderate (
Partial, Pick, Omit, Record) → Well-known transformations
- Advanced (Conditional, mapped, template literal) → Library code, framework types
- Expert (Recursive types, complex
infer chains) → Rarely — last resort
Stay at the lowest tier that solves your problem. If you can't explain what a type does in one sentence, it's too
complex — split it, simplify it, or use explicit interfaces.
Narrowing
- Prefer discriminated unions for variant types. Add a
kind or type literal field to each variant.
- Use exhaustive switches. Add
default: { const _exhaustive: never = value; return _exhaustive; } to catch
unhandled variants at compile time.
- Type predicates for reusable guards:
function isFish(pet: Animal): pet is Fish. Use when filtering arrays or in
multiple call sites.
- Assertion functions:
function assertIsError(value: unknown): asserts value is Error — use for validation at
boundaries.
typeof, instanceof, in — use JavaScript narrowing constructs; TypeScript understands them natively.
typeof null === "object" pitfall. Always check for null separately before typeof object checks.
in operator and optional properties: if Human has swim?, "swim" in animal narrows to Fish | Human, not
just Fish.
- Truthiness narrowing pitfall: fails on
"", 0, NaN, false. Prefer explicit null checks over truthiness when
these values are valid.
- Equality narrowing:
x === y narrows both to their common type. != null checks both null and undefined.
Enums
- Prefer union types over enums when values are simple string literals:
type Status = "active" | "inactive" is
simpler than enum Status.
- Prefer string enums over numeric enums when an enum is needed. String enums have meaningful runtime values and
readable debug output.
- Never use numeric enums with implicit values — always assign explicit values.
- Never coerce enums to booleans. Compare explicitly:
level !== Level.NONE, not !!level. Numeric enum value 0
is falsy.
- Never mix numeric and string members in the same enum.
- Use enums over unions when you need a runtime object (iteration, lookup), a namespace for related constants, or
reverse mapping.
Type Assertions
- Prefer annotations over assertions.
: Foo catches errors; as Foo hides them.
- Always use
as syntax, never angle brackets (<Foo>value) — angle brackets conflict with JSX.
- Assertions are justified when you genuinely know more than the compiler: values from
JSON.parse, DOM API
returning wider types, trusted external sources.
- Double assertions through
unknown: value as unknown as Foo. Never use any as the intermediate type.
Overloads
- Prefer union types over overloads when parameter types differ but logic is shared.
- Prefer optional parameters over overloads when signatures differ only in trailing params.
- When overloads are necessary, put specific signatures before general ones — TypeScript picks the first matching
overload.
Class Patterns
- Omit
public — it's the default. Only use public on non-readonly constructor parameter properties.
- Use
private for internal state, protected for subclass access.
- Use
readonly on properties never reassigned after construction.
- Use constructor parameter properties to avoid boilerplate:
constructor(private readonly db: Database) {}.
- Initialize fields where declared when possible:
private count = 0.
- Require
override keyword on overridden methods (noImplicitOverride).
Callback Types
- Use
void return for callbacks whose return value is ignored.
- Don't use optional parameters in callbacks — callers can always ignore extra args.
(data: unknown, elapsed: number) => void not (data: unknown, elapsed?: number) => any.
Branded Types
Use branded types for nominal-like safety when structural typing is too permissive: domain IDs (UserId vs OrderId),
validated strings (Email), units (Meters vs Kilometers). Pattern:
type UserId = string & { readonly __brand: unique symbol }. Keep the branding mechanism consistent across the project
— unique symbol is most robust.
Array Type Syntax
Use T[] for simple element types (string[], User[]). Use Array<T> for complex element types
(Array<string | number>). Same rule applies to readonly variants.
Configuration (tsconfig.json)
strict: true always. Non-negotiable. Also enable noUncheckedIndexedAccess and noImplicitOverride.
- Module resolution:
module: "NodeNext" when transpiling with tsc; module: "preserve" with external bundlers
(Vite, esbuild, Bun). Use verbatimModuleSyntax: true in both cases.
- Target:
es2022 (stable). Set lib to include dom for browser projects.
- Never
@ts-ignore. Use @ts-expect-error in tests only, with a comment. Never @ts-nocheck in production.
- Keep
tsconfig.json minimal. Use extends for shared configs. Separate tsconfig.build.json for builds
(excludes tests, scripts).
See ${CLAUDE_SKILL_DIR}/references/configuration.md for the full options catalog, library project setup, and project
structure guidance.
Application
When writing TypeScript code:
- Apply all conventions silently — don't narrate each rule being followed.
- Match the project's existing patterns (interface vs type preference, enum style).
- If an existing codebase contradicts a convention, follow the codebase and flag the divergence once.
When reviewing TypeScript code:
- Cite the specific issue and show the fix inline.
- Don't lecture — state what's wrong and how to fix it.
Integration
The javascript skill is a hard prerequisite. The JavaScript skill governs code patterns; this skill governs
type-level choices.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: xobotyi-cc-foundry-typescript3description: TypeScript4---56# TypeScript78<prerequisite>9This skill extends the JavaScript skill. You must load `javascript` first — naming,10ternary operator rules, async patterns, and module conventions are defined there and11not duplicated here.12</prerequisite>1314**Types encode intent. Let the compiler prove the rest.**1516TypeScript's value is in catching bugs at compile time. Write types that express your domain; let inference handle the17obvious. Never fight the type system — if you need `as` or `any`, the types are wrong.1819## References2021| Topic | Reference | Contents |22| -------------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------- |23| Generics, utility types, type-level programming | [`${CLAUDE_SKILL_DIR}/references/generics.md`] | Utility type tables, conditional/mapped type examples, infer, template literals |24| Narrowing, type guards, discriminated unions | [`${CLAUDE_SKILL_DIR}/references/narrowing.md`] | typeof/instanceof/in examples, exhaustive switch, type predicates, assertion fns |25| tsconfig options, module resolution, project setup | [`${CLAUDE_SKILL_DIR}/references/configuration.md`] | Base/strict/module configs, library setup, compiler directives, project structure |26| Branded types, overloads, class patterns, enums | [`${CLAUDE_SKILL_DIR}/references/patterns.md`] | Interface vs type examples, assertion patterns, enum anti-patterns, callback types |2728## Type Safety2930- **`strict: true` always.** No exceptions. It enables `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, and31 other critical checks.32- **`unknown` over `any`.** Use `unknown` and narrow with type guards. `any` disables type checking entirely. Reserve33 `any` for migration or test mocks only — document why.34- **No non-null assertions (`!`) without justification.** Prefer narrowing. If `!` is truly needed, add a comment35 explaining why the value cannot be null.36- **No type assertions (`as`) for object literals.** Use type annotations (`: Foo`) instead — assertions hide37 missing/extra property errors.3839### `unknown` vs `any` Decision4041- **Value from external source (API, JSON.parse, user input)** → `unknown`42- **Function accepts anything, passes through without touching** → `unknown`43- **Migrating JS to TS incrementally** → `any` (temporary, with comment)44- **Test mock that intentionally bypasses type checking** → `any` (with comment)4546### The `{}` Type4748`{}` means "any non-nullish value" — almost never what you want.4950- **`unknown`** → Everything (null, undefined, primitives, objects)51- **`object`** → Non-primitive, non-null values52- **`{}`** → Any non-nullish value (primitives included)53- **`Record<string, unknown>`** → Objects with string keys5455Prefer `unknown` for opaque values, `Record<string, unknown>` for dict-like objects, `object` when you need "any56non-primitive".5758## Type Annotations5960- **Omit trivially inferred types.** Don't annotate `const x: number = 5` or `const s: string = "hello"`. The compiler61 infers these correctly.62- **Annotate complex return types.** When inference produces opaque or wide types, annotate explicitly for readability.63- **Annotate function signatures at API boundaries.** Exported functions and public methods should have explicit64 parameter and return types.65- **Use `import type` for type-only imports.** Enforced by `verbatimModuleSyntax`. Use `export type` for type re-exports66 — required for `isolatedModules`.67- **Annotate for precision with structural types.** Annotate at declaration so errors appear where the bug is, not at68 distant call sites.6970## Interfaces and Types7172- **`interface` for object shapes.** Better error messages, IDE support, and performance.73- **`type` for everything else** — unions, intersections, tuples, function types, mapped/conditional types.74- **Decision rule:** Object shape with known properties? `interface`. Everything else? `type`. Pick one pattern per kind75 and stay consistent within a project.76- **No empty interfaces.** Use a branded type or discriminated union as a marker.77- **No `namespace`.** Use ES modules. `namespace` is legacy.78- **No wrapper types.** `string` not `String`, `number` not `Number`.79- **Use interfaces for data shapes, not classes.** A class used purely as a data shape adds unnecessary overhead.8081## Null Handling8283- **Prefer optional `?` over `| undefined`** for fields and parameters. `| undefined` forces callers to pass `undefined`84 explicitly.85- **Don't include `null`/`undefined` in type aliases.** Keep nullability at the use site:86 `function getUser(): User | null` not `type MaybeUser = User | null`.87- **Null narrowing:** `!= null` checks both null and undefined (the one valid `==` use). `?.` for optional access. `??`88 for defaults.8990## Generics9192- **Name type parameters descriptively** when meaning is non-obvious. `T` is fine for single-parameter generics; use93 `TKey`, `TValue`, `TItem` for multiple parameters.94- **Constrain generics** with `extends` when possible. `<T extends string>` is better than `<T>` if `T` must be a95 string.96- **Keep generic constraints tight** — `<T extends Record<string, unknown>>` is better than `<T extends object>` when97 you need string keys.98- **Don't add unused type parameters.** Every generic must appear in the signature.99- **Avoid return-type-only generics.** If a generic appears only in the return type, it cannot be inferred and forces100 callers to guess.101- **Let inference work.** Don't specify type arguments when the compiler can infer them: `identity("hello")` not102 `identity<string>("hello")`.103- **Use type parameters in constraints:** `<T, K extends keyof T>` to relate parameters.104- **Generic parameter defaults:** `interface Container<T, U = T[]>` — omitted type args fall back to the default.105- **`NoInfer<T>`** (TS 5.4+) prevents a parameter from being an inference site — use when a parameter should be106 constrained by other params, not drive inference.107108### Utility Types109110Prefer built-in utility types over hand-rolling equivalents. Key types: `Partial`, `Pick`, `Omit`, `Record`, `Exclude`,111`Extract`, `ReturnType`, `Parameters`, `Awaited`, `NoInfer`. Use explicit interfaces when the type represents a distinct112domain concept. See `${CLAUDE_SKILL_DIR}/references/generics.md` for the full catalog and usage guidance.113114Conditional types (`T extends U ? X : Y`), mapped types (`{ [P in keyof T]: ... }`), and template literal types115(`` `${T}Changed` ``) are advanced tools — use for library code and framework types. See116`${CLAUDE_SKILL_DIR}/references/generics.md` for distributive behavior, `infer`, modifier removal, and key remapping.117118### Complexity Budget119120- **Simple** (`interface`, `type` alias, union) → Always — default choice121- **Moderate** (`Partial`, `Pick`, `Omit`, `Record`) → Well-known transformations122- **Advanced** (Conditional, mapped, template literal) → Library code, framework types123- **Expert** (Recursive types, complex `infer` chains) → Rarely — last resort124125Stay at the lowest tier that solves your problem. If you can't explain what a type does in one sentence, it's too126complex — split it, simplify it, or use explicit interfaces.127128## Narrowing129130- **Prefer discriminated unions** for variant types. Add a `kind` or `type` literal field to each variant.131- **Use exhaustive switches.** Add `default: { const _exhaustive: never = value; return _exhaustive; }` to catch132 unhandled variants at compile time.133- **Type predicates for reusable guards:** `function isFish(pet: Animal): pet is Fish`. Use when filtering arrays or in134 multiple call sites.135- **Assertion functions:** `function assertIsError(value: unknown): asserts value is Error` — use for validation at136 boundaries.137- **`typeof`, `instanceof`, `in`** — use JavaScript narrowing constructs; TypeScript understands them natively.138- **`typeof null === "object"` pitfall.** Always check for `null` separately before `typeof` object checks.139- **`in` operator and optional properties:** if `Human` has `swim?`, `"swim" in animal` narrows to `Fish | Human`, not140 just `Fish`.141- **Truthiness narrowing pitfall:** fails on `""`, `0`, `NaN`, `false`. Prefer explicit null checks over truthiness when142 these values are valid.143- **Equality narrowing:** `x === y` narrows both to their common type. `!= null` checks both null and undefined.144145## Enums146147- **Prefer union types** over enums when values are simple string literals: `type Status = "active" | "inactive"` is148 simpler than `enum Status`.149- **Prefer string enums** over numeric enums when an enum is needed. String enums have meaningful runtime values and150 readable debug output.151- **Never use numeric enums with implicit values** — always assign explicit values.152- **Never coerce enums to booleans.** Compare explicitly: `level !== Level.NONE`, not `!!level`. Numeric enum value `0`153 is falsy.154- **Never mix numeric and string members** in the same enum.155- **Use enums over unions when** you need a runtime object (iteration, lookup), a namespace for related constants, or156 reverse mapping.157158## Type Assertions159160- **Prefer annotations over assertions.** `: Foo` catches errors; `as Foo` hides them.161- **Always use `as` syntax**, never angle brackets (`<Foo>value`) — angle brackets conflict with JSX.162- **Assertions are justified when** you genuinely know more than the compiler: values from `JSON.parse`, DOM API163 returning wider types, trusted external sources.164- **Double assertions through `unknown`:** `value as unknown as Foo`. Never use `any` as the intermediate type.165166## Overloads167168- **Prefer union types over overloads** when parameter types differ but logic is shared.169- **Prefer optional parameters over overloads** when signatures differ only in trailing params.170- **When overloads are necessary**, put specific signatures before general ones — TypeScript picks the first matching171 overload.172173## Class Patterns174175- **Omit `public`** — it's the default. Only use `public` on non-readonly constructor parameter properties.176- **Use `private`** for internal state, `protected` for subclass access.177- **Use `readonly`** on properties never reassigned after construction.178- **Use constructor parameter properties** to avoid boilerplate: `constructor(private readonly db: Database) {}`.179- **Initialize fields where declared** when possible: `private count = 0`.180- **Require `override` keyword** on overridden methods (`noImplicitOverride`).181182## Callback Types183184- **Use `void` return** for callbacks whose return value is ignored.185- **Don't use optional parameters in callbacks** — callers can always ignore extra args.186 `(data: unknown, elapsed: number) => void` not `(data: unknown, elapsed?: number) => any`.187188## Branded Types189190Use branded types for nominal-like safety when structural typing is too permissive: domain IDs (`UserId` vs `OrderId`),191validated strings (`Email`), units (`Meters` vs `Kilometers`). Pattern:192`type UserId = string & { readonly __brand: unique symbol }`. Keep the branding mechanism consistent across the project193— `unique symbol` is most robust.194195## Array Type Syntax196197Use `T[]` for simple element types (`string[]`, `User[]`). Use `Array<T>` for complex element types198(`Array<string | number>`). Same rule applies to readonly variants.199200## Configuration (tsconfig.json)201202- **`strict: true` always.** Non-negotiable. Also enable `noUncheckedIndexedAccess` and `noImplicitOverride`.203- **Module resolution:** `module: "NodeNext"` when transpiling with tsc; `module: "preserve"` with external bundlers204 (Vite, esbuild, Bun). Use `verbatimModuleSyntax: true` in both cases.205- **Target:** `es2022` (stable). Set `lib` to include `dom` for browser projects.206- **Never `@ts-ignore`.** Use `@ts-expect-error` in tests only, with a comment. Never `@ts-nocheck` in production.207- **Keep `tsconfig.json` minimal.** Use `extends` for shared configs. Separate `tsconfig.build.json` for builds208 (excludes tests, scripts).209210See `${CLAUDE_SKILL_DIR}/references/configuration.md` for the full options catalog, library project setup, and project211structure guidance.212213## Application214215When **writing** TypeScript code:216217- Apply all conventions silently — don't narrate each rule being followed.218- Match the project's existing patterns (interface vs type preference, enum style).219- If an existing codebase contradicts a convention, follow the codebase and flag the divergence once.220221When **reviewing** TypeScript code:222223- Cite the specific issue and show the fix inline.224- Don't lecture — state what's wrong and how to fix it.225226## Integration227228The **javascript** skill is a hard prerequisite. The JavaScript skill governs code patterns; this skill governs229type-level choices.230231---232> Converted and distributed by [TomeVault](https://tomevault.io/claim/xobotyi) — claim your Tome and manage your conversions.233<!-- tomevault:4.0:skill_md:2026-04-13 -->