Advanced TypeScript Tips for Better Code
By Merlin Hardisty•January 10, 2026•3 min read
Why TypeScript?
TypeScript has become the de facto standard for building large-scale JavaScript applications. It catches errors early, improves code documentation, and makes refactoring much safer.
Advanced Tips
1. Use Const Assertions
Const assertions give you more precise types:
// Without const assertion
const config = {
api: 'https://api.example.com',
timeout: 5000
};
// Type: { api: string; timeout: number }
// With const assertion
const config = {
api: 'https://api.example.com',
timeout: 5000
} as const;
// Type: { readonly api: "https://api.example.com"; readonly timeout: 5000 }
2. Discriminated Unions
Create type-safe state machines:
type LoadingState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string }
| { status: 'error'; error: Error };
function handleState(state: LoadingState) {
switch (state.status) {
case 'idle':
return 'Ready to load';
case 'loading':
return 'Loading...';
case 'success':
return `Data: ${state.data}`;
case 'error':
return `Error: ${state.error.message}`;
}
}
3. Template Literal Types
Create powerful string types:
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = '/users' | '/posts' | '/comments';
type Route = `${HTTPMethod} ${Endpoint}`;
// Route is now:
// "GET /users" | "POST /users" | "PUT /users" | ...
4. Utility Types
Master the built-in utility types:
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Make all properties optional
type PartialUser = Partial<User>;
// Pick specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit specific properties
type UserWithoutId = Omit<User, 'id'>;
// Make all properties readonly
type ReadonlyUser = Readonly<User>;
5. Branded Types
Create nominal types in a structural type system:
type UserId = string & { readonly brand: unique symbol };
type PostId = string & { readonly brand: unique symbol };
function getUserById(id: UserId) {
// ...
}
const userId = "user-123" as UserId;
const postId = "post-456" as PostId;
getUserById(userId); // ✓ OK
getUserById(postId); // ✗ Error
Best Practices
Enable Strict Mode
Always use strict mode in your tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
}
}
Avoid Any
The any type defeats the purpose of TypeScript. Use unknown instead:
// Bad
function process(data: any) {
return data.value; // No type checking
}
// Good
function process(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: unknown }).value;
}
throw new Error('Invalid data');
}
Use Type Guards
Create reusable type guards:
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value
);
}
Conclusion
TypeScript is a powerful tool that becomes even more valuable as you learn its advanced features. These tips will help you write more type-safe, maintainable code.