Type-Level Testing + Public API Design (Library Author Mindset)
How to keep TypeScript APIs stable: test your types, design exports intentionally, and avoid leaking internals that you can’t maintain.
Frontend Interview Team
March 01, 2026
What you’ll learn
- What “type-level testing” means (and why it matters)
- How to assert compile-time behavior using
@ts-expect-error - Public API rules: how not to export types you’ll regret
30‑second interview answer
Type-level tests ensure your TypeScript API behaves as intended: certain code should compile, and certain misuse should fail. You can do this with @ts-expect-error in a dedicated test file or tools like tsd. This matters for libraries because types are part of your public contract; a breaking type change can be as harmful as a runtime breaking change.
Type-level testing with @ts-expect-error
Create a file like types.test-d.ts or type-tests.ts (not shipped to runtime).
// Suppose this is your public API
export function createId(prefix: "usr" | "org"): `${"usr" | "org"}_${string}` {
return `${prefix}_${Math.random().toString(36).slice(2)}` as any;
}
const id = createId("usr");
// id should be a template literal type
// @ts-expect-error - invalid prefix
createId("post");The build should fail if the expected error disappears (meaning your types got too permissive).
Testing assignability (pragmatic style)
A simple helper pattern:
type Assert<T extends true> = T;
type IsEqual<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2) ? true : false;
type _1 = Assert<IsEqual<"a" | "b", "a" | "b">>;This is mostly for library authors—use it sparingly.
Public API design rules (high-signal)
Rule 1: don’t leak internal types
If you export an internal type, you’re committing to maintain it.
Bad:
export type InternalState = {
cache: Map<string, any>;
debug: boolean;
};Better: export minimal, stable types.
Rule 2: prefer “input/output types” over “implementation types”
Your consumers care about inputs/outputs, not internals.
Rule 3: make invalid states unrepresentable
Use unions and discriminants so consumers can’t pass nonsense.
Production rule of thumb
- Treat exported types as semver commitments.
- Add type-level tests for the most important parts of your API.
- Keep your public surface small.
Interview questions
-
Q: What is a type-level test?
- A: A compile-time assertion that a usage compiles or fails as expected.
-
Q: Why does it matter?
- A: Types are part of your public contract; regressions break consumers.
Quick recap
- Use
@ts-expect-errorto lock in behavior. - Don’t export types you can’t maintain.
- Small public surface area = long-term flexibility.