30 TypeScript Interview Questions: From Basics to Advanced

Vitaliy Obolenskiy β€” Founder of Skillhacker Β· June 6, 2026

Test yourself with 30 carefully curated TypeScript questions β€” from fundamental concepts to advanced type-level reasoning. This collection includes 15 easy, 8 medium, and 7 hard questions covering all major TypeScript topics.

Topics covered: Interfaces and Types, Generics, Modules, Variables and Declarations, Type System, Functions, Type Guards.

Use this for self-assessment, team quizzes, or interview preparation. Each question includes a detailed answer breakdown.

πŸ’‘ Want to test your knowledge interactively? Try the full TypeScript assessment on Skillhacker β€” get instant feedback, track your progress, and compare your results with other developers.


πŸ“š Table of Contents


Easy Questions

1. Using readonly to enforce immutability in interfaces

Topic: Interfaces and Types · Difficulty: ⭐ Easy

Given the TypeScript interface below, what happens when attempting to assign a new value to x after object creation?

interface Coordinate {
  readonly x: number;
  readonly y: number;
}

const c: Coordinate = { x: 5, y: 15 };
c.x = 20;
View Answer

The readonly modifier is a compile-time construct in TypeScript. Assigning to a readonly property triggers:

Error: TS2540: Cannot assign to 'x' because it is a read-only property.

Key points:

  • It does not affect runtime behavior
  • No JavaScript error is thrown unless additional runtime immutability (e.g., Object.freeze) is applied
  • It's purely for type-checking

πŸ“– Readonly properties (TypeScript Handbook)


2. Type parameter constraint with extends

Topic: Generics · Difficulty: ⭐ Easy

What does the extends keyword do in a generic type parameter declaration like <T extends string>?

View Answer

extends in generic constraints sets an upper bound: T must be assignable to the specified type.

Example:

function logLength<T extends string>(value: T): void {
  console.log(value.length); // βœ… Safe to access .length
}

logLength("hello"); // βœ… OK
logLength(42); // ❌ Error: number doesn't extend string

Valid types for T:

  • string
  • 'hello' (literal type)
  • string & { length: number }

Invalid: number, boolean, etc.

This enables safe access to shared properties.

πŸ“– Generic Constraints (TypeScript Handbook)


3. Purpose and characteristics of .d.ts files

Topic: Modules · Difficulty: ⭐ Easy

What is the primary purpose of a TypeScript declaration file (.d.ts)?

View Answer

Declaration files (.d.ts) contain only type information β€” no executable code.

Purpose:

  • Enable type checking for libraries without built-in TypeScript support
  • Provide autocompletion and IntelliSense in editors
  • Describe the shape of JavaScript modules

Key characteristics:

  • Not emitted as .js files
  • Have no runtime impact
  • Used by TypeScript compiler and IDEs

πŸ“– Declaration Files (TypeScript Handbook)


4. Purpose of the 'declare' keyword

Topic: Variables and Declarations · Difficulty: ⭐ Easy

What is the primary purpose of the declare keyword in TypeScript variable declarations?

View Answer

declare is used in ambient declarations to inform TypeScript that a variable exists at runtime without generating JavaScript code.

Common use cases:

  • External scripts or global scope variables
  • Libraries loaded via <script> tags
  • Global constants in .d.ts files

Example:

declare const API_KEY: string;
// TypeScript knows API_KEY exists, but no JS is emitted

Key points:

  • Has no runtime effect
  • Purely for type-checking
  • Commonly seen in .d.ts files

πŸ“– Declaration Files (TypeScript Handbook)


5. TypeScript array typing and type safety enforcement

Topic: Type System · Difficulty: ⭐ Easy

Consider this code:

let items: string[] = ['a', 'b'];
items.push(42); // ❌ error

Why does the second line produce a TypeScript error?

View Answer

The type annotation string[] means "array whose elements are all of type string".

What happens:

  • TypeScript enforces this at compile time
  • Calling .push(42) violates the constraint because 42 is not assignable to string
  • The error occurs during type checking, not at runtime

Important:

  • push() is fully supported on string[]
  • The array is mutable (not readonly) by default
  • Only the element type is restricted

πŸ“– Array Types (TypeScript Handbook)


6. Rest parameter type annotation

Topic: Functions · Difficulty: ⭐ Easy

How do you correctly type a rest parameter that accepts zero or more numbers?

View Answer

Rest parameters must be typed as an array:

// βœ… Correct
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

// ❌ Incorrect
function sum(...nums: number): number { ... }

Valid options:

  • ...nums: number[] (idiomatic, preferred)
  • ...nums: Array<number> (equivalent)

Invalid:

  • ...nums: number (not an array type)
  • [...number[]] (tuple rest syntax, not for standalone rest parameters)

πŸ“– Rest Parameters (TypeScript Handbook)


7. Literal types and type widening

Topic: Type System · Difficulty: ⭐ Easy

What is the difference between these two type declarations?

let a = "hello";
let b: string = "hello";
let c: "hello" = "hello";
View Answer

Key difference: literal types vs primitive types

let a = "hello":

  • TypeScript infers the literal type "hello" (with const it stays literal; with let, it widens to string)
  • With let, "hello" is widened to string on reassignment analysis

let b: string:

  • Type is string (any string value)
  • Can be reassigned to any string

let c: "hello":

  • Type is the literal type "hello" (only this exact value)
  • Can only be "hello", nothing else

Example:

let wide: string = "hello";
wide = "world"; // βœ… OK

let narrow: "hello" = "hello";
narrow = "world"; // ❌ Error: Type '"world"' is not assignable to type '"hello"'

Why it matters:

  • Literal types enable precise type constraints
  • Essential for discriminated unions
  • Powers type-safe enums and configuration objects

πŸ“– Literal Types (TypeScript Handbook)


8. Invalid type guard example

Topic: Type Guards · Difficulty: ⭐ Easy

Why does the following function NOT act as a type guard?

function isNumber(val: any): boolean {
  return typeof val === 'number';
}
View Answer

Although the function correctly checks typeof val === 'number', it returns boolean, not a type predicate.

Correct type guard:

function isNumber(val: any): val is number {
  return typeof val === 'number';
}

Why it matters:

  • Without val is number, TypeScript won't narrow val's type in calling code
  • The function works at runtime but provides no compile-time type safety

πŸ“– Type Predicates (TypeScript Handbook)


9. When to prefer unknown over any for untrusted input

Topic: Type System · Difficulty: ⭐ Easy

You're parsing JSON from an external API whose shape is undocumented. Which type best expresses "this value exists but we don't know its structure β€” and we must check before use"?

View Answer

Answer: unknown

unknown is the type-safe counterpart to any:

  • Represents a value whose type is truly unknown
  • TypeScript requires type narrowing before accessing properties
  • Prevents accidental misuse and catches errors at compile time

Example:

const data: unknown = JSON.parse(jsonString);

// ❌ Error: Object is of type 'unknown'
console.log(data.name);

// βœ… OK: Narrow first
if (typeof data === 'object' && data !== null && 'name' in data) {
  console.log((data as { name: string }).name);
}

Why not other options:

  • any disables type checking entirely
  • Object is too vague and doesn't prevent unsafe access
  • Record<string, unknown> is useful after narrowing to an object

πŸ“– Unknown Type (TypeScript Handbook)


10. Extending interfaces with extends keyword

Topic: Interfaces and Types · Difficulty: ⭐ Easy

Which TypeScript syntax correctly defines a new interface ColorSquare that extends the base interface Square and adds a color property?

View Answer

Correct syntax:

interface Square {
  side: number;
}

interface ColorSquare extends Square {
  color: string;
}

Key points:

  • extends is used to inherit members from one or more base interfaces
  • inherits is not a valid TypeScript keyword
  • implements is used by classes to satisfy an interface, not for interface extension

Note: While intersection types (&) can achieve similar structural results, they are not interface extension and don't support declaration merging.

πŸ“– Extending Interfaces (TypeScript Handbook)


11. Purpose of Partial utility type

Topic: Generics · Difficulty: ⭐ Easy

What does the Partial<Type> utility type do in TypeScript?

View Answer

Partial<Type> constructs a type where all properties of Type are marked as optional.

Example:

interface User {
  name: string;
  age: number;
  email: string;
}

type PartialUser = Partial<User>;
// Equivalent to:
// {
//   name?: string;
//   age?: number;
//   email?: string;
// }

Use cases:

  • Creating update or patch objects
  • Optional configuration objects
  • Partial form data

πŸ“– Partial (TypeScript Handbook)


12. Barrel file purpose

Topic: Modules · Difficulty: ⭐ Easy

What is the main purpose of a barrel file (e.g., index.ts) in a TypeScript project?

View Answer

A barrel file (typically index.ts) re-exports key members from sibling modules.

Before barrel file:

import { A } from './utils/a';
import { B } from './utils/b';
import { C } from './utils/c';

After barrel file (utils/index.ts):

import { A, B, C } from './utils';

Benefits:

  • Improves API surface control
  • Reduces import path complexity
  • Provides cleaner public API

πŸ“– TypeScript Module Patterns


13. Tuple destructuring and type safety

Topic: Variables and Declarations · Difficulty: ⭐ Easy

Given let record: [string, number] = ["Alice", 75000]; let [name, salary] = record;, what is the type of name after destructuring?

View Answer

Answer: string

Tuple destructuring preserves element types:

  • name is inferred as string
  • salary is inferred as number

Example:

let record: [string, number] = ["Alice", 75000];
let [name, salary] = record;

name = true; // ❌ Error: Type 'boolean' is not assignable to type 'string'

Key point: Destructuring does not widen or erase types.

πŸ“– Tuple Types (TypeScript Handbook)


14. Function type annotation placement

Topic: Functions · Difficulty: ⭐ Easy

In TypeScript, where is the return type annotation placed in a function declaration?

View Answer

The return type annotation appears after the parameter list and before the function body, separated by a colon.

Correct syntax:

function greet(name: string): string {
  return `Hello ${name}`;
}

// Arrow function
const greet = (name: string): string => `Hello ${name}`;

Key points:

  • Enforces that the function returns a value compatible with the declared type
  • JSDoc comments can provide hints but do not enforce types like native annotations

πŸ“– Function Types (TypeScript Handbook)


15. Union types and type narrowing

Topic: Type System · Difficulty: ⭐ Easy

Given this code, how does TypeScript know what type value is inside each branch?

function format(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else {
    return value.toFixed(2);
  }
}
View Answer

TypeScript uses control flow analysis to narrow union types.

What happens:

  • Initial type: string | number
  • After typeof value === "string": type narrows to string
  • In the else branch: type narrows to number (the remaining member)

Key points:

  • TypeScript tracks type information through control flow
  • Each branch sees only the narrowed type
  • No explicit type assertions required

Other narrowing techniques:

// in operator β€” object unions
if ("swim" in pet) { /* Fish */ }

// Discriminated unions
type Result =
  | { status: "success"; data: string }
  | { status: "error"; message: string };

if (result.status === "success") {
  result.data; // string
}

// Type predicates
function isString(x: unknown): x is string {
  return typeof x === "string";
}

πŸ“– Narrowing (TypeScript Handbook)


Medium Questions

16. Distinguishing optional parameters from union with undefined

Topic: Functions · Difficulty: ⭐⭐ Medium

What key difference exists between function f(x?: string) and function f(x: string | undefined)?

View Answer

Key difference: call-site requirements

function f(x?: string):

f(); // βœ… Valid - argument is truly optional
f("hello"); // βœ… Valid
f(undefined); // βœ… Valid

function f(x: string | undefined):

f(); // ❌ Error - parameter is required
f("hello"); // βœ… Valid
f(undefined); // βœ… Valid - must explicitly pass undefined

Important notes:

  • Both accept undefined as a value
  • Only ? changes call-site requirements
  • Neither permits null unless explicitly added to the union
  • strictNullChecks affects how undefined/null are treated in assignments, not parameter optionality

πŸ“– Optional Parameters (TypeScript Handbook)


17. Object type literal vs structural typing

Topic: Interfaces and Types · Difficulty: ⭐⭐ Medium

Consider this code:

let pt: { x: number; y: number } = { x: 10, y: 20 };
pt = { x: 5, y: 15, z: 30 }; // ❌ Error?

Why does the second assignment fail?

View Answer

TypeScript applies excess property checking to object literals assigned to typed variables.

What happens:

  • The literal must not contain properties not present in the target type
  • This helps catch typos and unintended properties

Important distinction:

// Object literal - checked strictly
let pt: { x: number; y: number } = { x: 5, y: 15, z: 30 }; // ❌ Error

// Variable assignment - excess properties allowed
let point3D = { x: 5, y: 15, z: 30 };
pt = point3D; // βœ… OK - structural typing

Why: Object literals are checked strictly to catch mistakes, while variables follow structural typing rules.

πŸ“– Excess Property Checks (TypeScript Handbook)


18. Narrowing union types with typeof vs in

Topic: Type Guards · Difficulty: ⭐⭐ Medium

When would you prefer typeof x === 'string' over 'prop' in x for narrowing a union type?

View Answer

Use typeof for primitive types:

function process(value: string | number) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase()); // βœ… value is string
  } else {
    console.log(value.toFixed(2)); // βœ… value is number
  }
}

Use in for object types:

interface Fish { swim(): void; }
interface Bird { fly(): void; }

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim(); // βœ… animal is Fish
  } else {
    animal.fly(); // βœ… animal is Bird
  }
}

Key difference:

  • typeof distinguishes primitive types (string, number, boolean, etc.)
  • in distinguishes object types by presence of a property
  • Using in on primitives causes a compile-time error

πŸ“– typeof Type Guards (TypeScript Handbook)


19. Trade-off of third-party library integration

Topic: Modules · Difficulty: ⭐⭐ Medium

When using a JavaScript library without built-in TypeScript support, what is a common challenge developers faceβ€”and how is it typically mitigated?

View Answer

Challenge: Many npm packages lack bundled type definitions.

Solutions (in order of preference):

  1. DefinitelyTyped (most common):

    npm install --save-dev @types/lodash
    
  2. Custom declaration files:

    // my-declarations.d.ts
    declare module 'some-library' {
      export function doSomething(): void;
    }
    
  3. Inline type assertions (last resort):

    import lib from 'some-library';
    const typedLib = lib as SomeInterface;
    

What NOT to do:

  • Disabling type checking defeats the purpose
  • Overusing any or unknown casts undermines type safety
  • Rewriting libraries isn't required β€” TypeScript supports JS interop natively

πŸ“– Declaration Files (TypeScript Handbook)


20. Using 'keyof' with generic type parameter

Topic: Generics · Difficulty: ⭐⭐ Medium

Given function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }, what does T[K] represent?

View Answer

T[K] is an indexed access type (also called a lookup type).

What it does:

  • Evaluates to the type of the property named by K in T
  • Because K is constrained to keyof T, this is type-safe and precise

Example:

interface User {
  name: string;
  age: number;
}

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user: User = { name: "Alice", age: 30 };

const name = getProperty(user, 'name'); // type: string
const age = getProperty(user, 'age'); // type: number

Why it's powerful:

  • Maintains type relationship between key and value
  • Powers utility types like Pick, Record, Omit

πŸ“– Indexed Access Types (TypeScript Handbook)


21. Effect of readonly on arrays in TypeScript

Topic: Variables and Declarations · Difficulty: ⭐⭐ Medium

What happens when you try to call .push() on a variable typed as readonly string[]?

View Answer

Result: Compile-time error

const items: readonly string[] = ['a', 'b'];
items.push('c'); // ❌ Error: Property 'push' does not exist on type 'readonly string[]'

Why:

  • TypeScript enforces immutability at compile time for readonly string[]
  • The .push() method is not available on readonly array types
  • No runtime error occurs because the code doesn't compile

Key points:

  • readonly is purely a compile-time construct
  • It has no runtime effect
  • The array is still mutable at runtime if you bypass TypeScript

Alternative: Use ReadonlyArray<string> (equivalent)

πŸ“– Readonly Arrays (TypeScript Handbook)


22. Type assertion necessity with 'unknown'

Topic: Type System · Difficulty: ⭐⭐ Medium

Why does the following code produce a TypeScript error?

let foo: unknown = "Hello";
let bar: string = foo;
View Answer

The unknown type is intentionally non-assignable to concrete types without narrowing.

Why:

  • Enforces type safety when dealing with untrusted values
  • Prevents accidental misuse

Solutions:

  1. Type assertion:

    let bar: string = foo as string;
    
  2. Control-flow narrowing:

    if (typeof foo === 'string') {
      let bar: string = foo; // βœ… OK
    }
    
  3. Type guard function:

    function isString(val: unknown): val is string {
      return typeof val === 'string';
    }
    
    if (isString(foo)) {
      let bar: string = foo; // βœ… OK
    }
    

Key point: unknown forces you to prove the type before using it.

πŸ“– Unknown Type (TypeScript Release Notes)


23. Difference between undefined and null in TypeScript

Topic: Type System · Difficulty: ⭐⭐ Medium

With strictNullChecks enabled, why are undefined and null treated as distinct types β€” and how does TypeScript use that for narrowing?

View Answer

In TypeScript, undefined and null are separate types, not assignable to each other or to other types without an explicit union.

Under strictNullChecks:

let name: string = undefined; // ❌ Error
let name: string = null;      // ❌ Error

let a: string | undefined = undefined; // βœ… OK
let b: string | null = null;           // βœ… OK
let c: string | undefined = null;      // ❌ Error β€” null β‰  undefined

TypeScript-specific behavior:

  • Optional properties (email?: string) imply string | undefined, not null
  • --strictNullChecks forces you to model absence explicitly in the type system
  • Narrowing treats them separately:
function greet(name: string | null | undefined) {
  if (name === undefined) return "Hello, guest";
  if (name === null) return "Hello, anonymous";
  return `Hello, ${name.toUpperCase()}`; // name: string
}

undefined vs null in practice:

  • undefined β€” missing value, uninitialized variable, omitted optional property
  • null β€” intentional absence, often from APIs that return null

Key point: Unlike JavaScript, TypeScript's type checker rejects passing null where only undefined is expected (and vice versa) unless the union includes both.

πŸ“– Null and Undefined (TypeScript Handbook)


Hard Questions

24. Type assertion vs interface casting

Topic: Interfaces and Types · Difficulty: ⭐⭐⭐ Hard

When working with a loosely typed API response, why is const user = data as UserInterface potentially unsafe compared to using a type guard or validation function?

View Answer

Type assertions like as UserInterface provide zero runtime safety.

What happens:

  • They merely tell the compiler to treat the value as conforming to the interface
  • No actual validation occurs
  • Interfaces are purely compile-time constructs

Example of the problem:

interface User {
  name: string;
  age: number;
}

const data = await fetch('/api/user').then(r => r.json());
const user = data as User; // ❌ Unsafe!

console.log(user.name.toUpperCase()); // πŸ’₯ Runtime error if name is missing

Safe alternatives:

  1. Runtime validation (Zod):

    import { z } from 'zod';
    
    const UserSchema = z.object({
      name: z.string(),
      age: z.number()
    });
    
    const user = UserSchema.parse(data); // βœ… Validates at runtime
    
  2. Type guard function:

    function isUser(data: unknown): data is User {
      return (
        typeof data === 'object' &&
        data !== null &&
        'name' in data &&
        'age' in data
      );
    }
    

Key point: Type assertions are for when you know more than the compiler. For untrusted data, use runtime validation.

πŸ“– Type Assertions (TypeScript Handbook)


25. Ambient module declaration vs. global augmentation

Topic: Modules · Difficulty: ⭐⭐⭐ Hard

Which scenario correctly uses an ambient module declaration (declare module 'x') instead of global augmentation (declare global { ... }) in TypeScript?

View Answer

Use declare module 'x' for:

  • Describing the shape of a module
  • Libraries lacking type definitions
  • Module-scoped namespaces

Example:

// lodash.d.ts
declare module 'lodash' {
  export function chunk<T>(array: T[], size?: number): T[][];
  export function compact<T>(array: (T | null | undefined)[]): T[];
}

// Now you can import it
import { chunk } from 'lodash';

Use declare global for:

  • Modifying global scope
  • Adding methods to built-in types
  • Extending global interfaces

Example:

declare global {
  interface Array<T> {
    customMethod(): void;
  }
  
  namespace NodeJS {
    interface ProcessEnv {
      API_KEY: string;
    }
  }
}

Key difference:

  • declare module is for module imports
  • declare global is for global scope modifications

πŸ“– Ambient Modules (TypeScript Docs)


26. Ambient declaration vs implementation in global scope

Topic: Variables and Declarations · Difficulty: ⭐⭐⭐ Hard

Which declaration correctly declares a globally accessible constant without causing a duplicate identifier error in a .ts file that may be included multiple times?

View Answer

Answer: declare const

Why:

  • Introduces an ambient declaration
  • Asserts existence without emitting JavaScript
  • Avoids duplicate definition errors during compilation

Example:

// global.d.ts
declare const API_VERSION: string;
declare function initializeApp(): void;

Why not other options:

  1. Plain const:

    const API_VERSION = '1.0'; // ❌ Emits JS, causes duplication
    
  2. export:

    export const API_VERSION = '1.0'; // ❌ Creates module-scoped binding, not global
    
  3. globalThis assignment:

    globalThis.API_VERSION = '1.0'; // ❌ Runtime-only, no type safety
    

Use cases:

  • Global constants in declaration files
  • Multi-entrypoint setups
  • Legacy script concatenation (--outFile)

πŸ“– Ambient Declarations (TypeScript Handbook)


27. Conditional types with infer keyword

Topic: Generics · Difficulty: ⭐⭐⭐ Hard

What does this conditional type do, and how does the infer keyword work?

type Awaited<T> = T extends Promise<infer U> ? U : T;
View Answer

This conditional type extracts the resolved type from a Promise.

How it works:

  • T extends Promise<infer U> β€” checks if T is assignable to Promise<something>
  • infer U tells TypeScript to infer the type parameter and bind it to U
  • ? U : T β€” if T is a Promise, return the inferred type U; otherwise return T unchanged

Examples:

type A = Awaited<Promise<string>>;  // string
type B = Awaited<Promise<number>>;  // number
type C = Awaited<string>;          // string (not a Promise)
type D = Awaited<Promise<Promise<boolean>>>; // boolean (with TS 4.5+ recursive Awaited)

More examples with infer:

// Extract function return type
type ReturnType<T> = T extends (...args: never[]) => infer R ? R : never;

// Extract array element type
type ElementType<T> = T extends (infer E)[] ? E : T;

// Extract constructor instance type
type InstanceType<T> = T extends new (...args: never[]) => infer R ? R : never;

Key points:

  • infer can only be used in the extends clause of conditional types
  • It creates a type variable that TypeScript infers from the pattern
  • Essential for advanced type-level programming and built-in utility types

πŸ“– Conditional Types (TypeScript Handbook)


28. Type guard scope and control flow limitations

Topic: Type Guards · Difficulty: ⭐⭐⭐ Hard

Consider this function using a type guard:

function process(data: string | number): string {
  if (typeof data === 'string') {
    return data.toUpperCase();
  }
  // Outside the if-block, what is `data`'s type?
  return data.toString();
}

Why does this code compile successfully despite data being used as number after the guard?

View Answer

Built-in type guards enable control flow analysis.

What happens:

  • Inside the if block: data is narrowed to string
  • After the if block: TypeScript narrows it to the remaining union member β€” number

Visual representation:

function process(data: string | number): string {
  // data: string | number
  
  if (typeof data === 'string') {
    // data: string (narrowed)
    return data.toUpperCase();
  }
  
  // data: number (narrowed to remaining type)
  return data.toString();
}

Key points:

  • This is not implicit widening or inference
  • It's precise, branch-local narrowing
  • Based on exhaustive checking of the union
  • Works with typeof, instanceof, in, and custom type guards

Limitation: Control flow analysis doesn't persist across function calls or complex control structures.

πŸ“– Control Flow Analysis (TypeScript Handbook)


29. Mapped types with key remapping

Topic: Generics · Difficulty: ⭐⭐⭐ Hard

What does this mapped type do, and how does the as clause work?

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person {
  name: string;
  age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }
View Answer

This mapped type creates getter methods with transformed property names.

How it works:

  • [K in keyof T] β€” iterates over all keys of T; K is each key in turn
  • as \get${Capitalize<string & K>}`` β€” key remapping (TypeScript 4.1+)
    • Transforms each key name
    • Capitalize is a built-in template literal type
    • string & K ensures K is treated as a string (not number or symbol)
  • : () => T[K] β€” each property becomes a function returning the original property type

Step-by-step for Person:

// K = "name"  β†’ key becomes "getName",  value: () => string
// K = "age"   β†’ key becomes "getAge",   value: () => number

type PersonGetters = {
  getName: () => string;
  getAge: () => number;
};

Other key remapping examples:

// Remove keys by filtering with never
type RemoveKindField<T> = {
  [K in keyof T as Exclude<K, "kind">]: T[K];
};

// Prefix all keys
type Prefixed<T> = {
  [K in keyof T as `prefix_${string & K}`]: T[K];
};

// Remap to optional
type PartialByKeys<T, K extends keyof T> = Omit<T, K> & {
  [P in K as P]?: T[P];
};

Key points:

  • Key remapping enables powerful type transformations
  • Works with template literal types
  • Can filter, rename, or compute new keys
  • Foundation for advanced utility types beyond Partial, Pick, and Omit

πŸ“– Key Remapping in Mapped Types (TypeScript Handbook)


30. Rest parameters and tuple types in function signatures

Topic: Functions · Difficulty: ⭐⭐⭐ Hard

Given function foo(...args: [number, string?, boolean]): void {}, which call is invalid according to TypeScript's rest parameter and tuple type rules?

View Answer

The rest parameter ...args: [number, string?, boolean] is typed as a fixed-length tuple.

Valid calls:

foo(1); // βœ… OK - [number]
foo(1, "hello"); // βœ… OK - [number, string]
foo(1, "hello", true); // βœ… OK - [number, string, boolean]
foo(1, undefined, true); // βœ… OK - optional parameter

Invalid call:

foo(1, "hello", true, "extra"); // ❌ Error - too many arguments

Why:

  • TypeScript treats it as equivalent to (arg0: number, arg1?: string, arg2: boolean) => void
  • Passing four arguments violates arity
  • Rest parameters with tuple types do not accept extra elements

To accept variable length:

// Use variadic tuple with rest element
function bar(...args: [number, ...string[]]): void {}
bar(1, "a", "b", "c"); // βœ… OK

πŸ“– Tuples with Rest Elements (TypeScript Release Notes)


🎯 Ready to Test Yourself?

You've just reviewed 30 TypeScript questions covering everything from basic syntax to advanced type-level programming. But reading about TypeScript and actually using it under pressure are two different things.

Take the interactive assessment on Skillhacker:

  • ⏱️ Timed mode β€” simulate real interview conditions
  • πŸ“Š Instant feedback β€” see your score immediately
  • πŸ“ˆ Compare your results β€” see how you stack up against other developers
  • πŸŽ“ Practice mode β€” learn at your own pace with detailed explanations

Start the TypeScript Assessment β†’


SkillHacker.io

SkillHacker is a practice platform built to help developers succeed in technical interviews - with curated questions, realistic tests, progress tracking, and offline mode so you can prepare anywhere.

Β© 2026 SkillHacker. All rights reserved.