Learn: Advance Typescript - Part 1

Concept-focused guide for Advance Typescript - Part 1 (no answers revealed).

~6 min read

Learn: Advance Typescript - Part 1
Explore more for “uiux”:

Overview

Welcome to our deep dive into advanced TypeScript concepts! In this article, we'll explore a range of powerful features and patterns that frequently arise in interviews and real-world TypeScript development. By the end, you'll have a stronger grasp of topics like conditional and mapped types, utility types, type inference, type narrowing, and more—enabling you to write safer, more expressive, and maintainable code.

Concept-by-Concept Deep Dive

Conditional Types and Type Inference (infer)

Conditional types in TypeScript allow you to create types that depend on a condition. They're written like T extends U ? X : Y, meaning: if type T extends type U, the result is type X; otherwise, it's type Y.

Using infer in Conditional Types

The infer keyword lets you introduce a new type variable within a conditional type. It’s often used to "pull out" or "infer" a type from a structure. For example, T extends (infer U)[] ? U : T means: if T is an array of something, extract the element type as U; otherwise, just use T.

How to Reason About It:

  • Identify the shape you’re checking (T extends (infer U)[]).
  • If T fits that pattern, U becomes the element type.
  • If not, fallback to the default.

Common Misconceptions:

  • Thinking infer can be used outside conditional types—it cannot.
  • Not realizing that infer can extract types from functions, arrays, promises, etc.

Utility Types: Partial, Omit, ReturnType, keyof

TypeScript provides several built-in utility types to transform or extract information from other types.

Partial<T>, Omit<T, K>, and ReturnType<T>

  • Partial<T>: Makes all properties in T optional.
  • Omit<T, K>: Constructs a type by picking all properties from T except those in K.
  • ReturnType<T>: Extracts the return type of a function type.

keyof and Mapped Types

The keyof operator yields a union of property names (as string or symbol types) for a given type. Mapped types, such as { [K in keyof T]: ... }, allow you to iterate over these keys and build new types.

Reasoning Steps:

  • Use keyof to get keys.
  • Use mapped types to transform each property.
  • Use utility types to combine or exclude properties as needed.

Common Misconceptions:

  • Confusing Partial and Omit.
  • Forgetting that Omit removes properties, while Partial makes them optional.

Literal Types and as const

TypeScript lets you create types that represent exact values (literals), not just general types like string or number.

The as const Assertion

Using as const transforms an array or object into a "readonly" structure with exact literal types for its values, rather than widening them to string, number, etc.

Benefits:

  • Preserves specific values for type safety (e.g., for discriminated unions or enums).
  • Prevents mutation (readonly).

Common Misconceptions:

  • Assuming as const is just syntactic sugar—it actually changes the type shape.

Type Narrowing and Type Guards

Type narrowing is the process of refining a variable's type within a block of code, typically using typeof, instanceof, or custom type guard functions.

Custom Type Guards

A function returning a type predicate (e.g., arr is string[]) lets TypeScript know how to narrow types inside control flow. For example, you can check if a variable is an array of strings, and then safely treat it as such after the check.

How to Write:

  • Return a boolean.
  • Use a type predicate in the return type (x is SomeType).

Common Misconceptions:

  • Forgetting to use the is syntax in the return type, which means TypeScript can't narrow the type for you.

Generic Constraints and Type Parameters

Generics allow you to write reusable, type-safe code. Sometimes you want to restrict the kinds of types that can be used as arguments—this is where constraints (extends) come in.

How to Use:

  • <T extends SomeType> restricts T to types that satisfy SomeType.
  • This enables access to specific properties or methods within the generic.

Common Pitfalls:

  • Omitting constraints leads to errors when accessing properties that may not exist.
  • Over-constraining generics can reduce flexibility.

Discriminated Unions and Exhaustiveness

Discriminated unions use a common literal property (like type: 'a' | 'b') to distinguish between shapes. Switch or if-else statements can then "narrow" the type based on this property.

Why This Matters:

  • Allows TypeScript to infer and check which properties are safe to access.
  • Enables exhaustive checking—if all cases are handled, the code is safer.

Misconceptions:

  • Forgetting to handle all cases, which can lead to runtime bugs.

Worked Examples (generic)

Example 1: Inferring an Array's Element Type

Suppose you have:

type ExtractElement<T> = T extends (infer U)[] ? U : T;
type Result = ExtractElement<string[]>; // What is Result?

Process:

  • T is string[], which matches (infer U)[] with U as string.
  • So, Result becomes string.

Example 2: Making All Properties Optional

Given:

interface User { id: number; name: string; }
type MaybeUser = Partial<User>;

Process:

  • MaybeUser has both id and name, but both are optional (id?: number; name?: string).

Example 3: Using as const for Precise Types

const directions = ['N', 'E', 'S', 'W'] as const;

Process:

  • Without as const, directions: string[].
  • With as const, directions: readonly ['N', 'E', 'S', 'W']—a tuple of exact string literals, readonly.

Example 4: Custom Type Guard

function isNumberArray(val: unknown): val is number[] {
    return Array.isArray(val) && val.every(item => typeof item === 'number');
}

Process:

  • Checks at runtime.
  • If true, within this block, TypeScript knows val is a number[].

Common Pitfalls and Fixes

  • Forgetting to Use Type Guards Properly: Without a correct return type like x is SomeType, narrowing won't occur.
  • Using Utility Types Incorrectly: Mixing up Omit vs Partial, or forgetting to specify which keys to omit.
  • Not Understanding as const: Assuming it only makes arrays readonly, when it also fixes literal types.
  • Improper Generic Constraints: Omitting or over-constraining generics leads to errors or loss of flexibility.
  • Misusing keyof and Mapped Types: Not realizing that keyof T only covers known properties, or misapplying mapped type syntax.
  • Discriminated Unions: Not exhausting all cases when narrowing by a discriminant property.

Summary

  • Conditional types and infer help you extract types from structures and build flexible type logic.
  • Utility types like Partial, Omit, and ReturnType are essential for transforming and interrogating types.
  • The as const assertion creates exact, readonly values and can improve type safety significantly.
  • Type guards and narrowing let you safely refine types at runtime, enabling more robust code.
  • Use generic constraints to ensure generics behave predictably and safely.
  • Mastering mapped types and keyof unlocks advanced type transformations and filtering.

With these concepts, you’re well-prepared for advanced TypeScript challenges and real-world scenarios—both in codebases and interviews!

Was this helpful?

Join us to receive notifications about our new vlogs/quizzes by subscribing here!