-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
221: Versioning Type Definitions with SemVer
I want to show you a scenario that has happened to me more than once. You're maintaining a shared utility library used by several other teams in your company. Everything is going great until one morning, your Slack starts blowing up because three different projects suddenly won't compile.
// In the consumer's code:
import { UserProfile } from '@company/auth-lib';
const updateProfile = (user: UserProfile) => {
console.log(user.username);
};
// Error: Property 'username' does not exist on type 'UserProfile'.
// Did you mean 'displayName'?
You check your recent changes. You didn't touch the runtime JavaScript logic at all. You only "cleaned up" the type definitions in the .d.ts files. You changed username: string to displayName: string to better match the database schema. Since you didn't change any actual logic, you pushed it as a minor version bump (v1.1.0 to v1.2.0). You thought, "It's just a type change; it can't possibly break the production build."
The Invisible Breaking Change
Here is the hard truth about TypeScript: a breaking change in your types is a breaking change for your users.
In the example above, the JavaScript code might still work if the underlying object still has a username property at runtime, but the TypeScript compiler is now blocking the build. If your users are using a caret range in their package.json (like ^1.1.0), npm will automatically pull in your v1.2.0 update, and their CI/CD pipeline will instantly fail.
When we talk about Semantic Versioning (SemVer), we usually think about the API surface—the functions and classes. But in a TypeScript project, your type definitions are your API surface. If you change a type in a way that forces the consumer to change their code to make it compile, you have introduced a breaking change.
Defining "Breaking" in Type Space
I've found it helpful to categorize type changes into three buckets to decide which version number to bump:
- Patch (0.0.x): Fixing a typo in a type comment or correcting a type that was objectively wrong (e.g., changing
numbertostringbecause the API actually returns a string) without changing how the user calls the function. - Minor (0.x.0): Adding a new optional property to an interface, adding a new overloaded function signature, or adding a new exported type. These are additive and don't break existing code.
- Major (x.0.0): Renaming a property, changing an optional property to required, narrowing a type (e.g., changing
stringto'admin' | 'user'), or removing an exported type entirely.
A common mistake I see is "narrowing." If you change a function argument from any to string, you might think you're just making the code safer. But if a user was passing a number into that function, their code just broke. That's a Major bump.
Versioning the .d.ts separately
If you are publishing a JavaScript library with separate TypeScript definitions (the @types/library-name pattern), you have an extra layer of complexity. Ideally, the version of the types should track the version of the library. However, if you find yourself needing to fix a type bug without releasing a new version of the JS logic, you can bump the patch version of the types package specifically.
My advice? Keep it simple. If you're shipping a TS-first library, treat your index.d.ts as the primary contract. If you change a line in that file and it causes a red squiggly line in a consumer's editor, you've just earned yourself a Major version bump.
📋 Practical Task
Audit the Version Bump for the "Payment-Core" Library
You are the lead maintainer for a library called payment-core. The current version is v2.4.1. You have made the following three changes to the type definitions in a single PR. Your task is to determine the correct next version number according to SemVer and justify why.
// CHANGE 1:
// From: interface Transaction { id: string; amount: number; }
// To: interface Transaction { id: string; amount: number; currency?: string; }
// CHANGE 2:
// From: function getStatus(id: string): string
// To: function getStatus(id: string): 'pending' | 'completed' | 'failed'
// CHANGE 3:
// From: interface Config { timeout: number; }
// To: interface Config { timeout: number; retryLimit: number; } // retryLimit is now REQUIRED
Instructions:
- State the new version number.
- Explain which specific change(s) forced that version bump.
- Identify which change was "additive" and which was "breaking."
There are no comments for now.