Skip to content

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.

Rajesh DhimanMarch 01, 20262 min read
On this page

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.
Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.