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.

F

Frontend Interview Team

March 01, 2026

2 min read
Type-Level Testing + Public API Design (Library Author Mindset)

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

  1. Q: What is a type-level test?

    • A: A compile-time assertion that a usage compiles or fails as expected.
  2. Q: Why does it matter?

    • A: Types are part of your public contract; regressions break consumers.

Quick recap

  • Use @ts-expect-error to lock in behavior.
  • Don’t export types you can’t maintain.
  • Small public surface area = long-term flexibility.