TypeScript type definition in summary

Summarize common type definition patterns in TypeScript as a quick reference.

Built-in primitive types

  • string: textual data, e.g. hello
  • number: all numbers including integers and floats, e.g. 100, 3.14
  • bigint: arbitrarily large integers, e.g. 12345678901234567890n
  • boolean: true or false values, e.g. true, false
  • symbol: unique and immutable value, e.g. Symbol('key')
  • undefined: a declared variable but not assigned a value, e.g. undefined
  • null: an intentional absence of any value, e.g. null
  • void: used as return type for functions without returning a value, e.g. function log(): void { console.log('Hello'); }
  • never: a value that never occurs, e.g. function fail(message: string): never { throw new Error(message); }
  • unknown: represents any value, but safer than any, must do type checking before using, e.g. let value: unknown = 1;
  • any: disable type checking, e.g. let a: any = 'hello'; a = 1;

Object types

  • Plain Object (object)
let person: object = { name: 'Andy', age: 30 };
  • Specific Object Shapes (via {})
let user: { name: string; age: number } = { name: 'Bob', age: 25 };
  • Interfaces
interface User {
  name: string;
  age: number;
}
let u: User = { name: 'Carol', age: 22 };
  • Enums
enum Color {
  Red,
  Green,
  Blue,
}
  • Type Aliases
type Point = { x: number; y: number };
  • Arrays (Array<T> or T[])
let scores: number[] = [90, 85, 100];
let names: Array<string> = ['Andy', 'Bob'];
  • Tuples
let point: [number, number] = [10, 20];
let userInfo: [string, number] = ['Andy', 30];
  • Classes
class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
const dog = new Animal('Doggo');
  • Functions (Function type)
let greet: (name: string) => string = (name) => `Hello, ${name}!`;
  • Callable Objects
interface Callable {
  (x: number): number;
  description: string;
}
const square: Callable = Object.assign((x: number) => x * x, { description: 'Squares a number' });
  • Constructors (new)
interface Constructor {
  new (s: string): object;
}
  • Index Signatures
interface StringMap {
  [key: string]: string;
}
let dict: StringMap = { key1: 'value1', key2: 'value2' };
  • Indexed Access Types
// Accessing Object Properties
interface Person {
  name: string;
  age: number;
}
type PersonName = Person['name'];  // `PersonName` is `string`
type PersonAge = Person['age'];    // `PersonAge` is `number`

// Accessing Array Elements
type Numbers = number[];
type FirstNumber = Numbers[0];  // `FirstNumber` is `number`

// Using With String Literal Types
type Colors = 'red' | 'green' | 'blue';
type ColorLength = Colors['length'];  // `ColorLength` is `number`

// Accessing Nested Object Properties
interface Car {
  brand: string;
  specs: {
    engine: string;
    wheels: number;
  };
}
type EngineType = Car['specs']['engine'];  // `EngineType` is `string`
type WheelCount = Car['specs']['wheels']; // `WheelCount` is `number`

// Conditional Types with Indexed Access
type PropertyType<T, K extends keyof T> = T[K];
interface Car {
  brand: string;
  year: number;
}
type CarBrand = PropertyType<Car, 'brand'>;  // `CarBrand` is `string`
type CarYear = PropertyType<Car, 'year'>;    // `CarYear` is `number`
  • Mapped Types
type ReadOnly<T> = {
  readonly [K in keyof T]: T[K];
};
  • Template Literal Types
type Greeting = `Hello ${string}`;
let greeting: Greeting = 'Hello world'; // Works fine
greeting = 'Hello John';                // Works fine
greeting = 'Hi John';                   // Error: Type 'Hi John' is not assignable to type 'Greeting'
  • Utility Types
Partial<T>
Required<T>
Readonly<T>
Pick<T, K>
Omit<T, K>
Record<K, T>
Exclude<T, U>
Extract<T, U>
NonNullable<T>
ReturnType<T>
Parameters<T>
Awaited<T>
// ...
  • Intersection Types
type Worker = User & { jobTitle: string };
  • Union Types
type Shape = Circle | Rectangle;

interface Circle {
  radius: number;
}

interface Rectangle {
  width: number;
  height: number;
}
  • Structural Typing (Duck Typing)
let obj: {name: string} = {name: 'Andy', age: 30}; // allowed!
  • Literal Object Types
type Status = {
  state: 'loading' | 'success' | 'error';
};
  • Special Types

    • object: Non-primitive type only (cannot be null or undefined).
    • {}: Almost any non-null/undefined value (even 42 is {}!).
    • Object: JavaScript's Object type: almost anything (even functions, arrays).
  • Special: Record<string, unknown> pattern

type LooseObject = Record<string, unknown>;

// equivalent to

type LooseObject = {
  [key: string]: unknown;
};

Interface in detail

  • Basic Interface
interface User {
  name: string;
  age: number;
}

const user: User = { name: 'Andy', age: 30 };
  • Optional Properties
interface User {
  name: string;
  age?: number;  // optional
}

const user1: User = { name: 'Andy' };
const user2: User = { name: 'Bob', age: 25 };
  • Readonly Properties
interface User {
  readonly name: string;
  age: number;
}

const user: User = { name: 'Andy', age: 30 };
user.name = 'Bob';  // Error: Cannot assign to 'name' because it is a read-only property.
  • Function Types in Interfaces
interface Greet {
  (name: string): string;
}

const greet: Greet = (name) => `Hello, ${name}`;
  • Extending Interfaces
interface Person {
  name: string;
}

interface Employee extends Person {
  employeeId: number;
}

const employee: Employee = { name: 'Andy', employeeId: 123 };
  • Interface with Index Signatures
interface Dictionary {
  [key: string]: string;
}

const myDict: Dictionary = {
  apple: 'A fruit',
  car: 'A vehicle'
};
  • Intersection with Other Types
interface Product {
  name: string;
  price: number;
}

type DiscountedProduct = Product & {
  discount: number;
};

const discounted: DiscountedProduct = { name: 'Shoes', price: 50, discount: 10 };
  • Interfaces with Classes
interface Movable {
  move(): void;
}

class Car implements Movable {
  move() {
    console.log('The car is moving!');
  }
}
  • Interface with Method Signatures
interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
}

const myCalculator: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};
  • Interfaces with Optional Method Signatures
interface Logger {
  log?: (message: string) => void;
}

const logger: Logger = {
  log: (message) => console.log(message)
};

const silentLogger: Logger = {};  // This is valid because `log` is optional
  • Interfaces with extends and Multiple Inheritance
interface Worker {
  work(): void;
}

interface Manager {
  manage(): void;
}

interface Supervisor extends Worker, Manager {}

const supervisor: Supervisor = {
  work() {
    console.log('Working');
  },
  manage() {
    console.log('Managing');
  }
};
  • Merging Interfaces
interface Person {
  name: string;
}

interface Person {
  age: number;
}

const person: Person = { name: 'Andy', age: 30 };
  • Interface with Type Parameters (Generics)
interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: 'Hello' };
const numberBox: Box<number> = { value: 42 };
  • Hybrid Types
interface Counter {
  (start: number): string;   // Function signature
  value: number;             // Property
}

const counter: Counter = (start: number) => `Count: ${start}`;
counter.value = 0;

Type in detail

  • Basic Type Aliases
type Name = string;

const userName: Name = 'Andy';  // Equivalent to 'string' but using the alias.
  • Union Types
type Status = 'loading' | 'success' | 'error';

const currentStatus: Status = 'loading';  // Valid
  • Intersection Types
type Person = {
  name: string;
  age: number;
};

type Employee = Person & {
  employeeId: number;
};

const employee: Employee = { name: 'Andy', age: 30, employeeId: 123 };
  • Literal Types
type Color = 'red' | 'green' | 'blue';

const color: Color = 'green';  // Valid
  • Tuple Types
type Point = [number, number];

const point: Point = [10, 20];  // Valid
  • Function Types
type Greet = (name: string) => string;

const greet: Greet = (name) => `Hello, ${name}`;
  • Optional Properties
type User = {
  name: string;
  age?: number;  // Optional property
};

const user1: User = { name: 'Andy' };
const user2: User = { name: 'Bob', age: 30 };
  • Readonly Properties
type User = {
  readonly name: string;
  age: number;
};

const user: User = { name: 'Andy', age: 30 };
user.name = 'Bob';  // Error: Cannot assign to 'name' because it is a read-only property.
  • Mapped Types
type Person = { name: string; age: number };

type ReadOnlyPerson = { readonly [K in keyof Person]: Person[K] };

const person: ReadOnlyPerson = { name: 'Andy', age: 30 };
person.name = 'Bob';  // Error: Cannot assign to 'name' because it is a read-only property.
  • Conditional Types
type IsString<T> = T extends string ? 'Yes' : 'No';

type A = IsString<string>;  // 'Yes'
type B = IsString<number>;  // 'No'
  • Type Inference
type User = { name: string; age: number };

const user = { name: 'Andy', age: 30 };  // TypeScript infers `User` type automatically
  • Type Aliases with Generics
type Box<T> = { value: T };

const stringBox: Box<string> = { value: 'Hello' };
const numberBox: Box<number> = { value: 42 };
  • Union of Object Types
type Success = { status: 'success'; data: string };
type Error = { status: 'error'; message: string };

type Response = Success | Error;

const response: Response = { status: 'success', data: 'Hello, world!' };
  • Intersection of Object Types
type Person = { name: string };
type Employee = { employeeId: number };

type Worker = Person & Employee;

const worker: Worker = { name: 'Andy', employeeId: 123 };
  • Type Guards and Type Aliases
type User = { name: string; age: number };
type Admin = { name: string; role: string };

function isUser(user: User | Admin): user is User {
  return (user as User).age !== undefined;
}

const person: User | Admin = { name: "Andy", age: 30 };
if (isUser(person)) {
  console.log(person.age);  // Safe, because it's narrowed to 'User'
}
  • Utility Types Using type
type User = { name: string; age: number };

// Partial<User> makes all properties optional
type PartialUser = Partial<User>;

// Pick<User, 'name'> picks specific properties
type UserName = Pick<User, 'name'>;

Choose between interface and type

  • Use interface for defining object shapes, classes, and when extensibility is needed (e.g., when you want to merge interfaces or extend them).
  • Use type for more complex types like unions, intersections, generics, and aliasing for non-object types like arrays, tuples, functions, or more advanced scenarios.

Comparison of keyof and typeof

  • keyof: returns a union of property names (keys) of an object type
type Person = {
  name: string;
  age: number;
};

type PersonKeys = keyof Person;  // "name" | "age"
  • typeof: returns the type of a value (like string, number, or any object type)
const userName = 'Andy';
type UserNameType = typeof userName;  // string

function greet(user: string) {
  return `Hello, ${user}`;
}
type GreetFunctionType = typeof greet;  // (user: string) => string
  • keyof and typeof used together
const person = {
  name: 'Andy',
  age: 30,
};

type PersonKeys = keyof typeof person;  // "name" | "age"

What exists in both value space and type space

  • Variables and Constants
const userName = 'Andy';  // `userName` is a **value** in value space and has the value `"Andy"`
type UserNameType = typeof userName;  // `typeof userName` is a **type** in type space (`string`)

const userAge = 30;  // `userAge` is a **value** in value space
type UserAgeType = typeof userAge;  // `typeof userAge` is a **type** in type space (`number`)
  • Functions
function greet(name: string): string {
  return `Hello, ${name}!`;
}

const greetType: typeof greet = greet;  // `greet` is a **value** in value space and has the function implementation.
type GreetType = typeof greet;  // `typeof greet` is a **type** in type space that describes the function's signature
  • Classes
class Person {
  constructor(public name: string, public age: number) {}
}

const person1 = new Person('Andy', 30);  // `person1` is a **value** in value space (an instance of `Person`)

type PersonType = typeof Person;  // `typeof Person` is a **type** in type space, representing the class constructor
type PersonInstanceType = Person;  // `Person` is also a **type** in type space, representing the type of instances created by the class
  • Enums
enum Status {
  Loading = 'loading',
  Success = 'success',
  Error = 'error'
}

const currentStatus = Status.Loading;  // `currentStatus` is a **value** in value space (the string "loading")

type StatusType = typeof Status;  // `typeof Status` is a **type** in type space (the enum itself)
type StatusValues = Status;  // `Status` is a **type** in type space, representing the enum values
  • Type Assertions
const someValue: any = 'Hello!';
const stringValue = someValue as string;  // `stringValue` is a **value** in value space, and we assert that it has a **type** `string` in type space

Non-erasable types

  • typeof Operator
const str = 'Hello';
type StrType = typeof str;  // 'string' is the type inferred.

// In runtime, `typeof str` will be a reference to the actual value (string "Hello").
  • Enums
enum Color {
  Red,
  Green,
  Blue
}

const colorValue = Color.Red;  // Color is a non-erasable value, it gets converted into an object.

after compilation:

var Color;
(function (Color) {
    Color[Color['Red'] = 0] = 'Red';
    Color[Color['Green'] = 1] = 'Green';
    Color[Color['Blue'] = 2] = 'Blue';
})(Color || (Color = {}));
  • const Assertions
const fruit = 'apple' as const;  // "apple" is a literal type, this type is non-erasable.

type FruitType = typeof fruit;  // "apple" is a non-erasable type because it will be part of the generated JS.
  • Classes and Class Instances
class Person {
  constructor(public name: string, public age: number) {}
}

const person1 = new Person('Andy', 30);
  • Interfaces (used for code generation)

Interfaces are erased at runtime in the generated JavaScript, but when used for code generation, they are non-erasable types because they ensure that type-checking is enforced during the compile-time and influence the structure of the code generated.

interface IShape {
  x: number;
  y: number;
}

Type assertion

  • Angle Bracket (<Type>) Syntax (old syntax)
let value: unknown = 'hello';
let strLength: number = (<string>value).length;
  • as Syntax (recommended, more modern syntax)
let value: unknown = 'hello';
let strLength: number = (value as string).length;
  • Non-null Assertion (!)
let someValue: string | null = 'Hello';

// TypeScript will give an error because `someValue` could be `null`.
let length = someValue.length;  // Error: Object is possibly 'null'.

// Using non-null assertion to tell TypeScript we're sure it's not null.
let length = someValue!.length;  // Works fine, assuming `someValue` is never null.
  • Definite Assignment Assertion (!)
class MyClass {
  name!: string;  // Use `!` to tell TypeScript this will be assigned before use.

  constructor() {
    // Initialization later
    this.name = 'John Doe';
  }

  printName() {
    console.log(this.name);  // Works fine, name is assigned before use.
  }
}

const myClassInstance = new MyClass();
myClassInstance.printName();  // Output: John Doe
  • Const Assertion
let point = { x: 10, y: 20 } as const;
  • Double Assertion
let someNumber: number = '123' as unknown as number;

Type predicate

function isString(value: any): value is string {
  return typeof value === 'string';
}

Comparison of type assertion and type predicate

  • Type Assertion: “I know better than TypeScript — just assume it’s the right type.”
  • Type Predicate: “I will check it properly, and TypeScript can trust my result.”
// type assertion
let data: any = 'hello';
let length: number = (data as string).length; // Tell TS: treat 'data' as string

// type predicate
function isString(value: any): value is string {
  return typeof value === 'string';
}
function example(value: any) {
  if (isString(value)) {
    console.log(value.toUpperCase()); // TS knows it's string safely here
  }
}

Type constraint

function logLength<T extends { length: number }>(value: T) {
  console.log(value.length);
}
logLength("hello");        // OK
logLength([1, 2, 3]);      // OK
logLength({ length: 10 }); // OK
logLength(42);             // ❌

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}
const person = { name: "Andy", age: 25 };
getProperty(person, "name");    // OK
getProperty(person, "height");  // ❌

Type satisfaction

const user = {
  name: 'Andy',
  age: 30,
} satisfies { name: string; age: number };
// This user object must have at least a name string property.
// But user still keeps all its actual properties (age) and literal types ("Andy").

type Color = "red" | "green" | "blue";
const theme = {
  primary: "red",
  secondary: "blue",
} satisfies Record<string, Color>;
// ✅ checked: all values must be 'red' | 'green' | 'blue'
// ✅ keeps literal types ("red", "blue") for each key

Comparison of type constraint and type satisfaction

Feature Keyword Used on Behavior Example
Type constraint extends Type parameter Restricts what type arguments can be used <T extends string>
Type satisfaction satisfies Value Checks that a value conforms to a type, keeps original inference obj satisfies MyType

Example of tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",                        // Modern JavaScript output
    "module": "ESNext",                        // Enables tree-shaking in bundlers like Webpack, Vite
    "moduleResolution": "Node",                // Node-style resolution for modules
    "lib": ["ES2020", "DOM"],                  // Include necessary standard libraries
    "allowJs": false,                          // Disallow JavaScript files for strict TypeScript-only projects
    "checkJs": false,                          // No type-checking for JS
    "jsx": "react-jsx",                        // Use "react-jsx" for React 17+ projects
    "sourceMap": true,                         // Generate source maps for debugging
    "declaration": true,                       // Emit .d.ts files (important for libraries)
    "declarationMap": true,                    // Include source map for declarations
    "outDir": "dist",                          // Output directory
    "rootDir": "src",                          // Root directory of your source files
    "strict": true,                            // Enable all strict type-checking options
    "noImplicitAny": true,                     // Disallow `any` unless explicitly stated
    "strictNullChecks": true,                  // Enforce null/undefined checks
    "noUnusedLocals": true,                    // Warn on unused local variables
    "noUnusedParameters": true,                // Warn on unused function parameters
    "noImplicitReturns": true,                 // Ensure all code paths return a value
    "noFallthroughCasesInSwitch": true,        // Prevent fall-through in switch statements
    "esModuleInterop": true,                   // Allow default imports from CommonJS
    "forceConsistentCasingInFileNames": true,  // Avoid case-related bugs on case-sensitive file systems
    "skipLibCheck": true                       // Speed up builds by skipping library type checks
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "build"]
}

Example of ts command

# Initialize a TS project
tsc --init

# Build project
tsc
tsc --project <path_to_tsconfig.json> # Compile with a specific tsconfig.json file

# Watch mode
tsc -w

# Type check only
tsc --noEmit

# Run TypeScript scripts
tsx src/index.ts
ts-node src/index.ts
ts-node --esm src/index.ts
node src/index.ts # Node.js version 22.6+
bun run src/index.ts
deno run src/index.ts
tsc && node dist/index.js

typescript javascript nodejs