skills/typescript-interface-vs-type/SKILL.md
Guidance on when to use `interface` vs `type` in TypeScript. Use when reviewing TypeScript code, choosing between interface and type alias, or evaluating type system usage.
npx skillsauth add Thomashighbaugh/opencode typescript-interface-vs-typeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
interface vs typeQuick-reference guide for choosing between interface and type aliases in TypeScript.
interface and type in new code| Criterion | Prefer interface | Prefer type |
|-----------|-------------------|---------------|
| Declaration merging | ✅ Yes — interfaces merge | ❌ No — type aliases error on duplicate |
| Extending other types | ✅ extends (cleaner errors) | ⚠️ & intersection (can mask conflicts) |
| Union/intersection of primitives | ❌ Not supported | ✅ type Status = 'idle' \| 'loading' |
| Mapped types / conditional types | ❌ Not supported | ✅ type Readonly<T> = { readonly [K in keyof T]: T[K] } |
| Tuple types | ⚠️ Works but verbose | ✅ type Pair = [string, number] |
| Performance (large unions) | ✅ Faster for object shapes | ⚠️ Can be slower with & on many types |
| Function types | ✅ interface Fn { (x: number): void } | ✅ type Fn = (x: number) => void |
| Class implements | ✅ Preferred | ✅ Works, but less idiomatic |
interface declarations with the same name in the same scope merge automatically:
interface User {
name: string;
}
interface User {
age: number;
}
// Result: User has both name AND age
type aliases cannot be redeclared:
type User = { name: string };
type User = { age: number }; // ❌ Error: Duplicate identifier 'User'
When to use merging: Augmenting third-party library types (e.g., adding properties to Window, ProcessEnv, or library module declarations).
interface extendsinterface Base { name: string }
interface Derived extends Base { age: number }
// Derived: { name: string; age: number }
type intersection (&)type Base = { name: string }
type Derived = Base & { age: number }
// Derived: { name: string; age: number }
interface A { x: string }
interface B { x: number }
// ❌ Error: 'x' declared with incompatible types
type A = { x: string }
type B = { x: number }
type C = A & B; // ✅ No error — x becomes `never` (string & number)
Rule of thumb: extends gives clearer compiler errors on conflicts. & silently produces never for conflicting properties, which can mask bugs.
interfaceimplements reads more naturally with interfaceinterface for objectstypetype Status = 'idle' | 'loading' | 'success' | 'error'type ID = string | numbertype Getters<T> = { [K in keyof T]: () => T[K] }type Point = [number, number, number]type Fn = ((a: string) => void) & ((b: number) => void)interface is generally faster for the compiler on object types — it uses a cached "declared" formtype intersections (&) can be slower on large, deeply nested unions because the compiler must evaluate the full intersectioninterface with extends over type with &"Use
interfaceuntil you need to usetype."
| Guide | Default for Objects | Default for Unions |
|-------|-------------------|-------------------|
| TypeScript Handbook | interface | type |
| Google TS Style | interface | type |
| Microsoft TS Style | interface | type |
| Prettier (no opinion) | — | — |
// ✅ interface — object shapes, public APIs, class contracts
interface Props {
title: string;
onClick: () => void;
}
// ✅ type — unions, tuples, mapped types, utility types
type Status = 'idle' | 'loading' | 'error';
type Pair<T> = [T, T];
type ReadonlyProps = Readonly<Props>;
// ⚠️ Both work for simple object types — pick one per project
// ✅ interface for extends, ✅ type for intersection
tools
Create valid, type-safe TypeScript tools for OpenCode — generates correct boilerplate, typed interfaces, and handler stubs. Use when building new tools for global config or per-project .opencode/tools/.
testing
Explore multiple solution branches in parallel, evaluate each, and recommend the best path. Use when the user explicitly invokes /ideation tree-of-thoughts or asks for "tree of thought" / "branching exploration".
testing
Run multiple independent reasoning passes and find consensus. Use when the decision is critically important, the stakes are high, or the user explicitly requests "run it multiple times" / "check consistency" / "self-consistency".
testing
Optimization by PROmpting — generate candidate prompt variations, test each against a benchmark, and report the best performer. Use when the user invokes /ideation opro or asks for "prompt optimization" / "OPRO".