10 Senior TypeScript Concepts You Should Understand Deeply


TypeScript isn't just about adding types; it's about building safer, smarter codebases. This post dives into 10 advanced TypeScript concepts that every senior engineer should know, with real-world context and minimal code examples.
1. Type System Fundamentals
At its core, TypeScript provides static typing on top of JavaScript. But that includes:
Type inference: TS guesses the type when possible
Annotations: You can manually add types
Literal types: Constrain a value to an exact string/number/etc.
let a = "hello"; // inferred as string
let b: number = 42; // explicit annotation
let role: "admin" = "admin"; // literal type
Type inference is powerful, but senior devs use annotations for clarity when intent matters.
2. Advanced Types
Understand how to combine and transform types:
Union:
string | number
Intersection:
A & B
Mapped types: Transform object types
Conditional types: Dynamic type logic
type ApiResponse<T> = T extends Error ? never : T;
type Keys = 'a' | 'b';
type Flags = {
[K in Keys]: boolean; // mapped type
};
These tools let you build flexible, reusable types, key for scaling apps.
3. Type Narrowing
TypeScript uses type guards to narrow unions at runtime:
function handle(input: string | number) {
if (typeof input === 'string') {
return input.toUpperCase(); // string here
}
return input.toFixed(2); // number here
}
Other tools:
typeof
,instanceof
'key' in obj
Custom type predicates:
function isUser(x: any): x is User {
return x && typeof x.name === 'string';
}
Mastering narrowing avoids unsafe as
casts and improves logic readability.
4. Interfaces vs Types
Both can define object shapes, but with subtle differences:
interface
is extendable, supports declaration mergingtype
is more flexible (unions, intersections)
interface User {
name: string;
}
type Admin = User & { role: 'admin' };
Use interface
for public APIs and extension. Use type
for composition and complex constructs.
5. Generics
Generics let you define reusable, type-safe components/functions:
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42);
Avoid any
. Use <T>
when the type depends on input or usage context; This helps enforce type relationships and reduce duplication.
6. Type Compatibility & Structural Typing
TypeScript uses structural typing; Types are compatible if their shapes match, not their names.
type Point = { x: number; y: number };
let pos = { x: 1, y: 2, z: 3 };
const p: Point = pos; // ✅ extra properties are OK
This is different from nominal typing (e.g. in Java or Go). It’s flexible, but requires discipline to avoid false assumptions.
7. Utility Types
Built-in helpers to transform types:
Partial<T>
– all optionalPick<T, K>
– only selected keysOmit<T, K>
– all but selected keysRecord<K, V>
– map keys to valuesReturnType<T>
– extract return type
type User = { name: string; age: number };
type UserPreview = Pick<User, 'name'>;
These are foundational in any real-world app, and you can create your own utility types as needed.
8. Type-Safe APIs
When working with external data (e.g. fetch
, axios
), define clear expectations:
type Post = { id: number; title: string };
async function getPost(): Promise<Post> {
const res = await fetch('/post');
const data: unknown = await res.json();
if (isPost(data)) return data;
throw new Error('Invalid response');
}
Use zod
, io-ts
, or custom validators to narrow unknown
safely, don't trust the API blindly.
This protects your app from runtime crashes due to bad API shapes.
9. Tooling & Config
Your tsconfig.json
is not just boilerplate. Some key flags:
"strict": true
— enables all safety checks"baseUrl"
+"paths"
— for cleaner imports"noUncheckedIndexedAccess"
— forces safe key lookups
Also, integrate:
ESLint +
@typescript-eslint
tsc --noEmit
for type-checking only
Senior devs don’t just use TS, they configure it well and understand what’s being enforced.
10. Working with Third-Party Types (DefinitelyTyped, @types/
packages)
Many libraries don’t ship their own types, especially older ones, so TypeScript relies on the community-maintained type definitions via DefinitelyTyped.
You’ll often install types like this:
npm install --save-dev @types/lodash
You should also know how to:
Inspect type definitions in
node_modules/@types/...
Augment types for modules that are partially typed or poorly typed
Write your own ambient declarations when types don’t exist
// types/custom.d.ts
declare module 'some-legacy-lib' {
export function doStuff(): void;
}
A senior dev doesn’t avoid broken types, they fix or patch them. This keeps your app safe and lets you use TypeScript with the entire JS ecosystem.
Final Thoughts
TypeScript gives you a powerful type system, but to really benefit, you need to go beyond annotations and into shaping logic with types, narrowing correctly, and designing safe API contracts.
These 10 topics are fundamental if you’re leading teams, maintaining large codebases, or aiming for senior roles.
Enjoyed this post?
Follow Tech & Travel Logs for reflections on tech, travel, and remote work life.
🌐 Learn more or get in touch at ezequias.me
Subscribe to my newsletter
Read articles from Ezequias directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
