TypeScript
Completed
-
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
96: Const Assertions with as const
I've seen this happen a dozen times when developers are setting up configuration files or constants for their apps. You define a set of values that you know will never change, you use const to declare the object, and then you're baffled when TypeScript screams at you the moment you try to use one of those values in a function.
type UserRole = 'admin' | 'editor' | 'viewer';
const ROLE_CONFIG = {
ADMIN: 'admin',
EDITOR: 'editor',
VIEWER: 'viewer',
};
function assignRole(role: UserRole) {
console.log(`Assigning role: ${role}`);
}
// ❌ TypeScript Error:
// Argument of type 'string' is not assignable to parameter of type 'UserRole'.
assignRole(ROLE_CONFIG.ADMIN);
The "String Widening" Headache
If you're looking at that code and thinking, "But I used const! Why does TypeScript think ROLE_CONFIG.ADMIN is just a generic string?", you've run into type widening.
Here is the deal: when you declare an object with const, TypeScript ensures you can't reassign the entire object to something else. However, it doesn't assume the properties inside that object are immutable. TypeScript assumes that you might want to change ROLE_CONFIG.ADMIN = 'something else' later on. Because of that, it "widens" the type from the literal 'admin' to the general type string.
Since UserRole is a strict union of three specific strings, a generic string is too broad. TypeScript can't guarantee that ROLE_CONFIG.ADMIN hasn't been changed to 'super-user' by some other piece of code, so it blocks the call to assignRole to keep you safe.
Locking it down with as const
To fix this, we use a const assertion. By adding as const to the end of the object expression, you're telling TypeScript: "Don't widen these types. Treat every single property as a literal, and make the whole thing read-only."
type UserRole = 'admin' | 'editor' | 'viewer';
const ROLE_CONFIG = {
ADMIN: 'admin',
EDITOR: 'editor',
VIEWER: 'viewer',
} as const; // <--- The magic happens here
function assignRole(role: UserRole) {
console.log(`Assigning role: ${role}`);
}
// ✅ This now works perfectly!
assignRole(ROLE_CONFIG.ADMIN);
When you add as const, a few things happen under the hood:
- The type of
ROLE_CONFIG.ADMINis no longerstring; it is exactly'admin'. - The object becomes
readonly. If you try to doROLE_CONFIG.ADMIN = 'editor', TypeScript will throw an error because you've explicitly told it this structure is immutable. - If this were an array, it would be converted from a mutable array (like
string[]) to areadonly tuple.
I generally use as const whenever I'm creating a lookup table, a set of fixed options, or any configuration object that serves as the "source of truth" for types elsewhere in my app. It saves you from having to manually type every single property of your config object while still getting 100% type safety.
📋 Practical Task
Refactoring the Theme Palette Configuration
You are working on a UI library where the theme colors are strictly defined. Currently, the THEME_PALETTE object is causing type errors because its values are being widened to string.
Your Task:
Apply a const assertion to the THEME_PALETTE object so that the applyColor function accepts the palette values without error. Ensure the object remains read-only.
type ThemeColor = 'primary-blue' | 'secondary-grey' | 'accent-gold';
const THEME_PALETTE = {
PRIMARY: 'primary-blue',
SECONDARY: 'secondary-grey',
ACCENT: 'accent-gold',
};
function applyColor(color: ThemeColor) {
console.log(`Applying color: ${color}`);
}
// This is currently failing! Fix it using 'as const'.
applyColor(THEME_PALETTE.PRIMARY); There are no comments for now.