10 Senior TypeScript Concepts You Should Understand Deeply

EzequiasEzequias
4 min read

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 merging

  • type 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 optional

  • Pick<T, K> – only selected keys

  • Omit<T, K> – all but selected keys

  • Record<K, V> – map keys to values

  • ReturnType<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

0
Subscribe to my newsletter

Read articles from Ezequias directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ezequias
Ezequias