FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

TypeScript Advanced Types: Generics, Utility Types, Mapped Types and Conditional Types

Master TypeScript's type system beyond the basics. Covers generics with constraints, utility types, mapped types, conditional types, template literal types, and type guards.

Yusuf SeyitoğluMarch 17, 20260 views10 min read

TypeScript Advanced Types: Generics, Utility Types, Mapped Types and Conditional Types

TypeScript's type system is far more expressive than most developers realize. Once you go beyond basic annotations and into generics, mapped types, and conditional types, you can write code that is simultaneously more flexible and more type-safe. These concepts appear in senior TypeScript interviews and are essential for building robust libraries and design systems.

Generics with Constraints

Generics let you write code that works across multiple types while preserving type information:

typescript
-- Basic generic function function identity<T>(value: T): T { return value; } identity<string>("hello"); -- T = string identity(42); -- T inferred as number -- Generic with constraint function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { id: 1, name: "Alice", email: "alice@example.com" }; getProperty(user, "name"); -- string (correct type inferred) getProperty(user, "id"); -- number getProperty(user, "age"); -- Error: "age" not in keyof typeof user -- Multiple type parameters function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 }; } const merged = merge({ name: "Alice" }, { age: 30 }); merged.name; -- string merged.age; -- number

Generic Classes and Interfaces

typescript
interface Repository<T, ID = number> { findById(id: ID): Promise<T | null>; findAll(): Promise<T[]>; save(entity: T): Promise<T>; delete(id: ID): Promise<void>; } class UserRepository implements Repository<User> { async findById(id: number): Promise<User | null> { return db.users.findById(id); } async findAll(): Promise<User[]> { return db.users.findAll(); } async save(user: User): Promise<User> { return db.users.save(user); } async delete(id: number): Promise<void> { await db.users.delete(id); } } -- Generic class class Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T | undefined { return this.items.pop(); } peek(): T | undefined { return this.items[this.items.length - 1]; } get size(): number { return this.items.length; } } const numStack = new Stack<number>(); numStack.push(1); numStack.push(2); numStack.pop(); -- number | undefined

Built-in Utility Types

TypeScript ships with a rich set of utility types for transforming existing types:

typescript
interface User { id: number; name: string; email: string; password: string; createdAt: Date; } -- Partial: all properties optional type UserUpdate = Partial<User>; -- { id?: number; name?: string; email?: string; ... } -- Required: all properties required (inverse of Partial) type RequiredUser = Required<Partial<User>>; -- Readonly: prevents mutation type ImmutableUser = Readonly<User>; const user: ImmutableUser = { id: 1, name: "Alice", email: "a@b.com", password: "x", createdAt: new Date() }; user.name = "Bob"; -- Error: Cannot assign to 'name' because it is a read-only property -- Pick: select specific properties type UserPublic = Pick<User, "id" | "name" | "email">; -- { id: number; name: string; email: string; } -- Omit: exclude specific properties type UserWithoutPassword = Omit<User, "password">; -- { id: number; name: string; email: string; createdAt: Date; } -- Record: map keys to a type type RolePermissions = Record<"admin" | "editor" | "viewer", string[]>; const permissions: RolePermissions = { admin: ["read", "write", "delete"], editor: ["read", "write"], viewer: ["read"], }; -- Exclude / Extract: filter union types type Status = "pending" | "active" | "suspended" | "deleted"; type ActiveStatus = Exclude<Status, "deleted" | "suspended">; -- "pending" | "active" type InactiveStatus = Extract<Status, "suspended" | "deleted">; -- "suspended" | "deleted" -- NonNullable: remove null and undefined type MaybeString = string | null | undefined; type DefiniteString = NonNullable<MaybeString>; -- string -- ReturnType: extract function return type async function fetchUser(): Promise<User> { ... } type FetchUserReturn = Awaited<ReturnType<typeof fetchUser>>; -- User -- Parameters: extract function parameter types function createUser(name: string, email: string, age: number): User { ... } type CreateUserParams = Parameters<typeof createUser>; -- [string, string, number]

Mapped Types

Mapped types transform every property of an existing type:

typescript
-- Make all properties nullable type Nullable<T> = { [K in keyof T]: T[K] | null; }; type NullableUser = Nullable<User>; -- { id: number | null; name: string | null; ... } -- Make all properties optional getters/setters type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; type UserGetters = Getters<Pick<User, "name" | "email">>; -- { getName: () => string; getEmail: () => string; } -- Validation schema type type ValidationRules<T> = { [K in keyof T]?: { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp; }; }; const userValidation: ValidationRules<User> = { name: { required: true, minLength: 2, maxLength: 50 }, email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }, }; -- Remapping keys with as type EventHandlers<T> = { [K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void; }; type FormHandlers = EventHandlers<Pick<User, "name" | "email">>; -- { onNameChange: (value: string) => void; onEmailChange: (value: string) => void; }

Conditional Types

Types that depend on a condition β€” like ternary operators for types:

typescript
-- Basic conditional type type IsString<T> = T extends string ? true : false; type A = IsString<string>; -- true type B = IsString<number>; -- false -- Unwrap array element type type ElementType<T> = T extends (infer U)[] ? U : never; type StrElement = ElementType<string[]>; -- string type NumElement = ElementType<number[]>; -- number type NotArray = ElementType<string>; -- never -- Unwrap Promise type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T; type R1 = Awaited<Promise<string>>; -- string type R2 = Awaited<Promise<Promise<number>>>; -- number -- Conditional + mapped: filter properties by type type PickByType<T, ValueType> = { [K in keyof T as T[K] extends ValueType ? K : never]: T[K]; }; type StringFields = PickByType<User, string>; -- { name: string; email: string; password: string; } type NumberFields = PickByType<User, number>; -- { id: number; }

Template Literal Types

String manipulation at the type level:

typescript
type EventName = "click" | "focus" | "blur"; type Handler = `on${Capitalize<EventName>}`; -- "onClick" | "onFocus" | "onBlur" type CSSProperty = "margin" | "padding"; type CSSDirection = "top" | "right" | "bottom" | "left"; type CSSSpacing = `${CSSProperty}-${CSSDirection}`; -- "margin-top" | "margin-right" | ... | "padding-left" -- Route builder type safety type ApiRoute = "/users" | "/posts" | "/comments"; type ApiMethod = "GET" | "POST" | "PUT" | "DELETE"; type Endpoint = `${ApiMethod} ${ApiRoute}`; -- "GET /users" | "POST /users" | "GET /posts" | ... function callApi(endpoint: Endpoint): void { ... } callApi("GET /users"); -- valid callApi("GET /invalid"); -- Error!

Type Guards

Narrow types at runtime safely:

typescript
-- typeof guard function format(value: string | number): string { if (typeof value === "string") { return value.toUpperCase(); -- TypeScript knows: string } return value.toFixed(2); -- TypeScript knows: number } -- instanceof guard function handleError(error: unknown): string { if (error instanceof Error) { return error.message; -- TypeScript knows: Error } return String(error); } -- Custom type predicate interface Cat { meow(): void; } interface Dog { bark(): void; } function isCat(animal: Cat | Dog): animal is Cat { return "meow" in animal; } function makeSound(animal: Cat | Dog): void { if (isCat(animal)) { animal.meow(); -- TypeScript knows: Cat } else { animal.bark(); -- TypeScript knows: Dog } } -- Discriminated union guard type Shape = | { kind: "circle"; radius: number } | { kind: "rectangle"; width: number; height: number } | { kind: "triangle"; base: number; height: number }; function area(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "rectangle": return shape.width * shape.height; case "triangle": return 0.5 * shape.base * shape.height; } }

Common Interview Questions

Q: What is the difference between interface and type in TypeScript?

Both define types. Interfaces support declaration merging (you can reopen an interface to add properties) and are slightly better for object shapes you plan to extend. Type aliases support unions, intersections, conditional types, and mapped types. In practice, use interface for object shapes and public API contracts; use type for unions, utility types, and complex type transformations.

Q: What is a mapped type and when would you use one?

A mapped type iterates over the keys of an existing type to create a new type, transforming properties in some way. Common uses: making all fields optional (Partial), readonly (Readonly), or nullable; building form validation schemas; generating event handler types. They eliminate repetition β€” instead of manually writing out every property, you describe the transformation once.

Q: What is infer in TypeScript?

infer is used inside conditional types to capture (infer) a type from a matched position. For example, T extends Promise<infer U> ? U : never extracts the resolved type of a Promise. It is how ReturnType, Parameters, Awaited, and many utility types are implemented internally.

Practice TypeScript on Froquiz

Advanced TypeScript types are tested in senior frontend and full-stack interviews. Test your JavaScript and TypeScript knowledge on Froquiz across all difficulty levels.

Summary

  • Generics with extends constraints restrict what types can be used while keeping flexibility
  • Built-in utility types (Partial, Omit, Pick, Record, ReturnType) cover the most common transformations
  • Mapped types transform every property of a type β€” essential for building type-safe abstractions
  • Conditional types (T extends U ? X : Y) enable type-level logic and filtering
  • Template literal types enable type-safe string composition
  • Type guards (typeof, instanceof, is predicates, discriminated unions) narrow union types safely
  • infer inside conditional types extracts inner types from generic or complex types

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • PostgreSQL Full-Text Search: tsvector, tsquery, Ranking and Multilingual SearchMar 17
  • Python Testing with pytest: Fixtures, Parametrize, Mocking and Best PracticesMar 17
  • Software Architecture Patterns: MVC, Clean Architecture, Hexagonal and Event-DrivenMar 17
All Blogs