Skip to content
Back to skills

Type Safety

ASecurity

Model data so illegal states will not compile, then turn the checker to strict and let it prove correctness for free. Use when designing types, modeling state machines, or hardening a loosely typed module.

  • 7 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 5, 2026
ai-agentstypescriptpythongokotlinapi

Works with

  • api

Security analysis

A100/100

Scanned September 5, 2026

npx -y skills add Amey-Thakur/AI-SKILLS --skill type-safety --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Type Safety?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Type Safety
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/amey-thakur-type-safety/badge)](https://www.skillsdirectory.com/skills/amey-thakur-type-safety)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: type-safety
description: Model data so illegal states will not compile, then turn the checker to strict and let it prove correctness for free. Use when designing types, modeling state machines, or hardening a loosely typed module.
---

# Type safety

A type is a proof the compiler rechecks for free on every build, but only
about the things you let it see. Model with loose types (everything a
string, every field optional, states tracked by scattered flags) and the
compiler cannot help; model tightly and whole categories of bug stop
compiling.

## Method

1. **Make illegal states unrepresentable.** Replace a `status: string` plus
   a nullable `error` with a tagged union: `{ kind: 'loading' } | { kind:
   'ok', data: T } | { kind: 'error', message: string }`. Now "ok with an
   error message" cannot be constructed, so no code has to guard against
   it.
2. **Parse, do not validate, at the edge.** Turn input into a precise type
   once (`Email`, `UserId`, `NonEmptyList`) with zod, io-ts, or pydantic,
   then pass that type inward. A function taking `Email` never re-checks the
   `@`, because the type already carries the proof.
3. **Wrap primitives that mean different things.** A `UserId` and an
   `OrderId` that are both bare `string` will get swapped eventually.
   Branded types, newtypes, or `NewType` in Python make the swap a compile
   error at no runtime cost.
4. **Keep required fields required.** Make a field optional only when
   absence is a real, handled case. Every `?` you add is a branch every
   caller inherits, so keep the mandatory fields mandatory and let the type
   document what must exist.
5. **Turn the checker to strict.** `strict: true` in tsconfig, mypy
   `--strict`, Kotlin explicit-API mode, the relevant `-Werror` analyses.
   Half-on type checking passes files a full pass would reject, which is
   worse than knowing they are unsound.
6. **Make exhaustiveness a compile error.** Switch on a union with a
   `never` default (TypeScript) or a sealed `when` (Kotlin) so adding a
   variant fails the build at every site that does not yet handle it. The
   compiler becomes your checklist for the change.

## Checks

- Can you construct a value that is in two contradictory states at once? If
  so, tighten the type.
- Does adding a new enum case break the build at every site that must
  change?
- Are `UserId` and `OrderId` distinguishable to the compiler, or both just
  `string`?

## Boundaries

Dynamic languages without gradual typing, and seams with untyped systems
(raw JSON, FFI, reflection), cap how much the compiler can prove: validate
at those seams instead. Type modeling has a cost, and past a point the
ceremony outweighs the bugs prevented, so stop tightening when the next
constraint costs more than the mistake it would block.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…