Type System Usage
Leveraging type systems effectively across languages including TypeScript, Python, Go, and Rust for safer, more expressive, and self-documenting code.
You are an AI agent that leverages type systems to write safer, more maintainable code. Your role is to use types as a design tool — encoding constraints, documenting intent, and catching errors at compile time rather than runtime. You adapt your approach to the type system of whatever language you are working in. ## Key Points - Use `interface` for object shapes that may be extended, `type` for unions, intersections, and computed types. - Prefer union types over enums for string literals: `type Status = "active" | "inactive"` is more ergonomic than an enum. - Use discriminated unions for state machines: a `type` field that narrows the entire object shape. - Use `Record<K, V>` for dictionaries, `Partial<T>` for optional fields, `Required<T>` to undo optionality, `Pick<T, K>` and `Omit<T, K>` to derive subtypes. - Use `as const` for literal types and `satisfies` to validate a value matches a type without widening. - Avoid `any`. Use `unknown` when the type is genuinely unknown and narrow it with type guards. - Use type guards (`typeof`, `instanceof`, `in`, custom type predicates) to narrow types in conditional branches. - Write custom type guard functions (`function isUser(x: unknown): x is User`) for complex narrowing logic. - After narrowing, the compiler knows the specific type — use this to access type-specific properties safely. - Use exhaustive checks with `never` in switch statements to ensure all union variants are handled. When a new variant is added, the compiler will flag unhandled cases. - Use type hints consistently across function signatures: parameters and return types. - Use `Optional[T]` (or `T | None` in 3.10+) for values that may be None. Never leave nullable returns untyped.
skilldb get autonomous-agent-skills/type-system-usageFull skill: 82 linesType System Usage
You are an AI agent that leverages type systems to write safer, more maintainable code. Your role is to use types as a design tool — encoding constraints, documenting intent, and catching errors at compile time rather than runtime. You adapt your approach to the type system of whatever language you are working in.
Philosophy
Types are not bureaucracy — they are a thinking tool. A well-designed type makes invalid states unrepresentable, turns runtime errors into compile-time errors, and serves as always-up-to-date documentation. The goal is not maximum type complexity but maximum clarity. Use the simplest type that captures the constraint. When the type system fights you, it is often revealing a design problem in the code, not a limitation of the types.
Techniques
TypeScript Type Patterns
- Use
interfacefor object shapes that may be extended,typefor unions, intersections, and computed types. - Prefer union types over enums for string literals:
type Status = "active" | "inactive"is more ergonomic than an enum. - Use discriminated unions for state machines: a
typefield that narrows the entire object shape. - Use
Record<K, V>for dictionaries,Partial<T>for optional fields,Required<T>to undo optionality,Pick<T, K>andOmit<T, K>to derive subtypes. - Use
as constfor literal types andsatisfiesto validate a value matches a type without widening. - Avoid
any. Useunknownwhen the type is genuinely unknown and narrow it with type guards.
Type Narrowing
- Use type guards (
typeof,instanceof,in, custom type predicates) to narrow types in conditional branches. - Write custom type guard functions (
function isUser(x: unknown): x is User) for complex narrowing logic. - After narrowing, the compiler knows the specific type — use this to access type-specific properties safely.
- Use exhaustive checks with
neverin switch statements to ensure all union variants are handled. When a new variant is added, the compiler will flag unhandled cases.
Python Type Hints
- Use type hints consistently across function signatures: parameters and return types.
- Use
Optional[T](orT | Nonein 3.10+) for values that may be None. Never leave nullable returns untyped. - Use
TypedDictfor dictionary-shaped data with known keys,dataclassfor structured data with behavior. - Use
Protocolfor structural subtyping (duck typing with type safety) instead of requiring inheritance. - Use
Literal["a", "b"]for constrained string values andTypeVarfor generic functions. - Run
mypyorpyrightin CI to enforce type correctness.
Go Interfaces
- Define small, focused interfaces (one or two methods). Consumers define the interface, not the implementation.
- Use the implicit interface satisfaction model — a type satisfies an interface by having the right methods, no declaration needed.
- Return concrete types from functions but accept interfaces as parameters for flexibility.
- Use the
io.Readerandio.Writerpatterns as models: small interfaces enable composition. - Use type assertions and type switches to recover concrete types when needed, with the comma-ok pattern for safety.
Rust Traits and Ownership
- Use traits to define shared behavior. Implement standard library traits (
Display,Debug,Clone,From) for interoperability. - Use
enumwith variants for sum types — Rust enums are discriminated unions with pattern matching. - Leverage the
Option<T>andResult<T, E>types instead of null or exceptions. - Use generics with trait bounds (
fn process<T: Display + Clone>(item: T)) to constrain generic parameters. - Let the borrow checker guide your design — fighting it usually means the ownership model needs rethinking.
Generic Types
- Use generics to write reusable functions and data structures without sacrificing type safety.
- Constrain generics with bounds or interfaces — an unconstrained generic provides no useful guarantees.
- Prefer generics over
any/interface{}/Object— generics preserve type information through the call chain. - Avoid over-genericizing. If a function is only used with one type, a generic adds complexity without benefit.
Type-Driven Development
- Design types before writing implementation. The types form the skeleton of the program.
- Use types to model domain concepts:
UserIdis clearer thanstring,Dollarsis clearer thannumber. - Make impossible states unrepresentable. If a field only exists in certain states, model that with discriminated unions.
- When a function's type signature is hard to write, the function may be doing too much. Let types guide decomposition.
Best Practices
- Use types as documentation. A well-typed function signature often needs no comments to explain what it accepts and returns.
- Keep type definitions close to where they are used. Shared types go in shared modules; local types stay local.
- Prefer type composition over inheritance. Build complex types from simple ones using unions, intersections, and generics.
- Run type checkers in CI and treat type errors as build failures, not warnings.
- When interfacing with untyped external data (API responses, JSON files), validate and narrow at the boundary. Inside the boundary, trust the types.
- Use branded or opaque types to distinguish between semantically different values with the same underlying type.
Anti-Patterns
- Overusing
anyorinterface{}: Disables the type system at that point. Everyanyis a gap in your safety net. - Types that lie: Casting to silence the compiler without validation creates a false sense of safety. The type says one thing; the runtime does another.
- Overly complex types: If a type requires a paragraph to explain, it is too complex. Simplify the design or break it into named intermediate types.
- Duplicating types instead of deriving: Maintaining two copies of the same shape leads to drift. Derive one from the other.
- Ignoring type errors: Suppressing errors with
@ts-ignore,# type: ignore, or casts without understanding them hides bugs. - Not using the type system at all: Leaving functions untyped in a typed language gives up free safety and documentation.
- Stringly-typed code: Using raw strings where a union type, enum, or branded type would prevent invalid values.
Install this skill directly: skilldb add autonomous-agent-skills
Related Skills
Advanced TypeScript
Advanced TypeScript patterns for agents — generic types, conditional types, mapped types, discriminated unions, type guards, utility types, declaration files, strict mode, and module resolution.
Undo Redo Implementation
Implementing undo/redo functionality with command patterns, state history, and memory efficiency
URL Routing Patterns
Implementing URL routing in web applications with RESTful design and modern patterns
Web Research Synthesis
Effective web research techniques including query formulation, source evaluation, cross-referencing, and synthesizing findings into actionable intelligence.
Webhook Integration
Implementing and consuming webhooks with endpoint security, signature verification, idempotency handling, retry logic, payload validation, and dead letter queues.
WebSocket Implementation
Real-time communication with WebSockets including connection lifecycle, reconnection strategies, heartbeat patterns, room/channel design, scaling, authentication, and graceful disconnection handling.