| Feature | JavaScript | TypeScript |
|---|---|---|
| Type annotation | function add(a, b) {} | function add(a: number, b: number): number {} |
| Interface | N/A | interface User { name: string; } |
| Type alias | N/A | type ID = string | number; |
| Union type | N/A | let value: string | number; |
| Generics | N/A | function identity<T>(x: T): T {} |
| Enum | N/A | enum Status { Active, Inactive } |
| Readonly | N/A | readonly property: string; |
| Intersection | N/A | type Combined = A & B; |
| Nullable | N/A | let value: string | null; |
| Optional chaining | ?.valid | value?.property?.nested (typed) |
TypeScript Essential Cheat Sheet
Comprehensive reference guide for TypeScript including type system, interfaces, generics, decorators, and professional development best practices.
Quick Reference
Most commonly used TypeScript features at a glance
About TypeScript
TypeScript adds static typing to JavaScript, catching errors at compile-time rather than runtime. It's fully compatible with JavaScript and compiles to clean, readable JS that runs everywhere.
Basic Types
Primitive type annotations
Primitive Types
Core TypeScript types
// String
const name: string = 'John';
const greeting: string = `Hello, ${name}!`;
// Number
const age: number = 30;
const price: number = 19.99;
const hex: number = 0xFF;
// Boolean
const active: boolean = true;
const isAdmin: boolean = false;
// Any (avoid when possible)
let anything: any = 42;
anything = 'string'; // No error
// Unknown (type-safe alternative)
let unknown: unknown = 42;
if (typeof unknown === 'number') {
console.log(unknown + 1); // OK
}
// Void (no return value)
function log(): void {
console.log('Message');
}
// Never (unreachable code)
function throwError(msg: string): never {
throw new Error(msg);
}
// Symbol
const sym: symbol = Symbol('id');
// Null and Undefined
const nothing: null = null;
const undef: undefined = undefined;Best Practice
Avoid
any at all costs - use unknown instead. Use strict type checking to catch errors early.Type Aliases & Unions
Create custom types
Type Aliases
Define reusable types
// Simple type alias
type ID = string | number;
const userId: ID = '12345';
// String literal type
type Status = 'active' | 'inactive' | 'pending';
const userStatus: Status = 'active';
// Union types
type Result = string | number | boolean;
let result: Result = true;
// Union with type guard
function process(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
return (value * 2).toString();
}
// Template literal types
type EndPoints = `/api/${string}`;
const endpoint: EndPoints = '/api/users';
// Discriminated unions
type Response =
| { status: 'success'; data: any }
| { status: 'error'; error: string }
| { status: 'loading' };
const handleResponse = (res: Response) => {
if (res.status === 'success') {
console.log(res.data);
} else if (res.status === 'error') {
console.log(res.error);
}
};Interfaces
Define object shapes and contracts
Interface Basics
Define object structures
// Basic interface
interface User {
id: number;
name: string;
email?: string; // Optional property
active: boolean;
}
// Use interface
const user: User = {
id: 1,
name: 'John',
active: true
};
// Optional and readonly
interface Config {
readonly apiUrl: string;
timeout?: number;
retries: number;
}
// Method signature
interface Logger {
log(message: string): void;
error(message: string): Error;
}
// Index signature (dynamic keys)
interface Dictionary {
[key: string]: string;
}
const dict: Dictionary = {
hello: 'world',
foo: 'bar'
};
// Extend interfaces
interface Admin extends User {
role: 'admin';
permissions: string[];
}
// Multiple inheritance
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Document extends User, Timestamped {
content: string;
}Advanced Interfaces
Complex interface patterns
// Generic interface
interface Repository<T> {
getAll(): Promise<T[]>;
getById(id: number): Promise<T>;
create(item: T): Promise<T>;
update(id: number, item: T): Promise<T>;
delete(id: number): Promise<void>;
}
// Callable interface
interface CallableFunc {
(x: number): number;
property: string;
}
// Constructor signature
interface Constructor<T> {
new (...args: any[]): T;
}
// Hybrid type
interface API {
(config: string): void;
baseUrl: string;
version: string;
}
// Conditional types with interfaces
interface Response<T> {
data: T;
status: T extends null ? 'empty' : 'full';
}
// Function overloading in interface
interface Converter {
convert(value: string): number;
convert(value: number): string;
convert(value: Date): string;
}
// Mapped types
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};Interface vs Type
Use
interface for object shapes and contracts. Use typefor unions, tuples, and primitive aliases.Classes
Object-oriented programming with TypeScript
Class Declaration & Modifiers
Define and use classes with type safety
// Basic class
class User {
// Property with type
id: number;
name: string;
email: string;
// Constructor
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
// Method
greet(): string {
return `Hello, ${this.name}!`;
}
}
// Parameter properties (shorthand)
class Profile {
constructor(
public id: number,
public name: string,
private email: string,
readonly createdAt: Date
) {}
}
// Visibility modifiers
class Config {
public setting: string = 'default'; // Accessible everywhere
protected secret: string = 'hidden'; // In class and subclasses
private apiKey: string = 'secret'; // Only in this class
readonly version: string = '1.0'; // Can't be changed
}
// Abstract class
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('Moving...');
}
}
class Dog extends Animal {
makeSound(): void {
console.log('Woof!');
}
}
// Getters and setters
class Account {
private _balance: number = 0;
get balance(): number {
return this._balance;
}
set balance(amount: number) {
if (amount >= 0) {
this._balance = amount;
}
}
}
// Static members
class Math {
static PI = 3.14159;
static add(a: number, b: number): number {
return a + b;
}
}
console.log(Math.PI);
console.log(Math.add(5, 3));Best Practice
Use parameter properties (public/private/protected in constructor) to reduce boilerplate. Mark unchanging properties as
readonly.Generics
Reusable type-safe code
Generic Functions & Classes
Create flexible, type-safe components
// Generic function
function identity<T>(value: T): T {
return value;
}
const strResult = identity<string>('hello');
const numResult = identity<number>(42);
// Generic with constraint
function getLength<T extends { length: number }>(
value: T
): number {
return value.length;
}
getLength('hello'); // 5
getLength([1, 2, 3]); // 3
// Generic class
class Box<T> {
constructor(private content: T) {}
getContent(): T {
return this.content;
}
}
const stringBox = new Box<string>('hello');
const numberBox = new Box<number>(42);
// Multiple type parameters
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
// Default generic type
function createArray<T = string>(length: number): T[] {
return new Array(length);
}
// Generic with keyof
function getProperty<T, K extends keyof T>(
obj: T,
key: K
): T[K] {
return obj[key];
}
const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name'); // stringAdvanced Generics
Complex generic patterns
Advanced Generic Techniques
Powerful type manipulation
// Generic constraints
type StringOrNumber = string | number;
function process<T extends StringOrNumber>(
value: T
): T {
return value;
}
// Conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
// Extract from union
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
// Generic async
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json();
}
const users = await fetchData<User[]>('/api/users');
// Generic with indexed access
type GetReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Partial and Required
type Optional<T> = {
[K in keyof T]?: T[K];
};
type Mandatory<T> = {
[K in keyof T]-?: T[K];
};
// Pick and Omit
type UserPreview = Pick<User, 'name' | 'email'>;
type UserWithoutPassword = Omit<User, 'password'>;
// Record type
type Roles = 'admin' | 'user' | 'guest';
const permissions: Record<Roles, string[]> = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read']
};Intersection & Utility Types
Combine and manipulate types
Intersection Types & Built-in Utilities
Advanced type composition
// Intersection type (&)
interface Named {
name: string;
}
interface Aged {
age: number;
}
type Person = Named & Aged;
const person: Person = {
name: 'John',
age: 30
};
// Utility types - Partial
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
const update: PartialUser = { name: 'Jane' };
// Readonly
type ReadonlyUser = Readonly<User>;
const frozen: ReadonlyUser = {
id: 1,
name: 'John',
email: 'john@example.com'
};
// frozen.name = 'Jane'; // Error!
// Pick - select properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit - exclude properties
type UserWithoutEmail = Omit<User, 'email'>;
// Record - create object with specific keys
type Status = 'active' | 'inactive';
const statusConfig: Record<Status, string> = {
active: 'User is active',
inactive: 'User is inactive'
};
// Exclude - remove from union
type NonString = Exclude<string | number | boolean, string>;
// NonString = number | boolean
// Extract - select from union
type StringOrNumber = Extract<string | number | boolean, string | number>;
// StringOrNumber = string | number
// NonNullable - remove null/undefined
type Nullable<T> = T | null | undefined;
type NonNull<T> = NonNullable<Nullable<T>>;
// ReturnType - get return type
type MyFunc = () => string;
type FuncReturn = ReturnType<MyFunc>; // string
// InstanceType - get instance type
type MyClass = new () => User;
type UserInstance = InstanceType<MyClass>; // UserUtility Types
Master
Partial, Pick, Omit, and Record - they are fundamental for managing complex type hierarchies.Decorators
Metadata and function enhancement
Decorator Syntax
Add metadata to classes and methods
// Enable decorators in tsconfig.json
// "experimentalDecorators": true
// Class decorator
function Serializable(constructor: Function) {
constructor.prototype.toJSON = function() {
return JSON.stringify(this);
};
}
@Serializable
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// Method decorator
function Deprecated(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const oldMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.warn(`Method ${propertyKey} is deprecated`);
return oldMethod.apply(this, args);
};
return descriptor;
}
class API {
@Deprecated
oldMethod() {
return 'Old implementation';
}
}
// Property decorator
function Validate(target: any, propertyKey: string) {
let value: string;
const getter = () => value;
const setter = (newValue: string) => {
if (newValue.length < 3) {
throw new Error('Too short');
}
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
}
class Account {
@Validate
username: string = '';
}Namespaces & Modules
Organize code effectively
Module System
Import/export with types
// Export types
export interface User {
id: number;
name: string;
}
export type UserID = number;
export class UserService {
getUser(id: UserID): User {
// Implementation
return { id, name: 'John' };
}
}
// Default export
export default UserService;
// Import variations
import UserService, { User, UserID } from './user';
// Import entire module
import * as UserModule from './user';
// Type-only import (avoids circular deps)
import type { User } from './user';
// Re-export
export { User, UserID } from './user';
export * as UserTypes from './user';
// Namespace (rarely used now)
namespace Geometry {
export interface Point {
x: number;
y: number;
}
export function distance(a: Point, b: Point) {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
}
const point1: Geometry.Point = { x: 0, y: 0 };Advanced Type Features
Powerful type manipulation techniques
Conditional & Mapped Types
Dynamic type generation
// Conditional type
type IsString<T> = T extends string
? true
: false;
type A = IsString<'hello'>; // true
type B = IsString<number>; // false
// Nested conditional
type Flatten<T> =
T extends Array<infer U>
? U
: T extends object
? T
: never;
type Test = Flatten<string[]>; // string
// Mapped types
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]:
() => T[K]
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// {
// getName: () => string;
// getAge: () => number;
// }
// As const (literal types)
const config = {
API_URL: 'https://api.example.com',
TIMEOUT: 5000
} as const;
type ConfigKey = keyof typeof config;
// 'API_URL' | 'TIMEOUT'Template Literal Types
Create types from string patterns
// Template literal types
type EndPoint = `/api/${string}`;
const endpoint: EndPoint = '/api/users';
// Union combinations
type Method = 'get' | 'post' | 'put' | 'delete';
type Route = `${Method} /${string}`;
const route: Route = 'get /users';
// Type inference
function useAPI<T extends string>(
method: Method,
endpoint: T
): `${Method} ${T}` {
return `${method} ${endpoint}`;
}
// Labeled tuple types
type Response = [
status: number,
data: string,
error?: Error
];
const response: Response = [200, 'Success'];
// Readonly tuple
type ReadonlyTuple = readonly [
readonly string[],
readonly number[]
];
// Variadic tuple types
type Concat<T extends readonly unknown[]> =
readonly [...T, T];
type Result = Concat<[string, number]>;
// readonly [string, number, string, number]Type Guards & Type Narrowing
Refine types safely
Type Guards & Narrowing Techniques
Safely work with union types
// typeof guard
function process(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
return (value * 2).toString();
}
// instanceof guard
class User {}
class Admin extends User {}
function handleUser(user: User | Admin) {
if (user instanceof Admin) {
user.permissions; // Admin properties
}
}
// Custom type guard (type predicate)
interface Car {
wheels: 4;
drive(): void;
}
interface Bike {
wheels: 2;
pedal(): void;
}
function isCar(vehicle: Car | Bike): vehicle is Car {
return (vehicle as Car).drive !== undefined;
}
const vehicle: Car | Bike = getCar();
if (isCar(vehicle)) {
vehicle.drive(); // TypeScript knows it's a Car
}
// Property checking
type Admin = { role: 'admin'; deleteUser(): void };
type User = { role: 'user' };
function handleRole(user: Admin | User) {
if ('deleteUser' in user) {
user.deleteUser();
}
}
// Discriminated union
type Result =
| { status: 'success'; data: any }
| { status: 'error'; error: string };
function handle(result: Result) {
switch (result.status) {
case 'success':
console.log(result.data);
break;
case 'error':
console.log(result.error);
break;
}
}
// is operator (type assertion guard)
const value: string | number = '42';
if (typeof value === 'string' && value.includes('4')) {
console.log('Contains 4');
}Type Assertions
Use
as sparingly. Prefer type guards for safety.as const is an exception - it's safe and useful.Function Signatures & Overloading
Advanced function typing
Function Overloading & Signatures
Define multiple function signatures
// Function signature (no implementation)
declare function greet(name: string): string;
// Full function
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Overloading (multiple signatures)
function format(date: Date): string;
function format(date: Date, format: string): string;
function format(date: Date, format?: string): string {
if (format) {
return date.toString(); // Use format
}
return date.toString();
}
format(new Date()); // Works
format(new Date(), 'yyyy-MM-dd'); // Works
// Function with this context
interface Button {
text: string;
onClick(this: Button, e: Event): void;
}
// Generic function signature
interface Observer<T> {
onNext(value: T): void;
onError(error: Error): void;
onComplete(): void;
}
// Rest parameters with types
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
// Variadic tuple parameters
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
function f1(...args: StringNumberBooleans) {
// args: [string, number, ...boolean[]]
}
// Function type expression
type Fn = (a: string) => number;
const fn: Fn = (str) => str.length;
// Construct signature
type Constructor<T> = {
new(...args: any[]): T;
};Modern Best Practices
Write professional TypeScript code
| Practice | Description | Example |
|---|---|---|
| Strict mode | Enable strict type checking | "strict": true in tsconfig.json |
| Avoid any | Use unknown or proper types | const value: unknown = 42; |
| Use readonly | Prevent accidental mutations | readonly property: string; |
| Type inference | Let TypeScript infer types | const name = 'John'; // string |
| Generic constraints | Limit generic parameters | T extends { length: number } |
| Discriminated unions | Safe union type handling | { status: 'success' | 'error' } |
| Type guards | Narrow types safely | if (typeof x === 'string') |
| Extract types | Reuse types from values | type User = typeof userObj; |
| Namespace types | Organize related types | namespace API { export type ... } |
| Utility types | Use built-in helpers | Partial<T>, Pick<T, K>, Record<K, V> |
| Interface over type | Prefer for object shapes | interface User { } |
| Strict null checks | Handle null/undefined | strictNullChecks: true |
Configuration Best Practices:
// tsconfig.json - Recommended strict settings
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
}
}Type Organization:
// Keep types organized
// types/index.ts
export type ID = string | number;
export type Status = 'active' | 'inactive';
export interface User {
id: ID;
name: string;
status: Status;
}
// services/user.ts
import type { User, ID } from '../types';
export class UserService {
async getUser(id: ID): Promise<User> {
// Implementation
}
}
// Use type-only imports to reduce bundle
import type { User } from '../types';Performance & Optimization
Write efficient TypeScript
| Issue | Problem | Solution |
|---|---|---|
| Large unions | Type checking is slow | Use discriminated unions instead |
| Deep nesting | Complex type hierarchy | Simplify with utility types |
| Any usage | Loses type information | Use proper types or unknown |
| Circular deps | Compilation issues | Use type-only imports |
| Bundle size | Extra code compiled | Use tree-shaking, split code |
| Reflection | Runtime overhead | Design without heavy reflection |
| Decorators | Adds metadata overhead | Use only when needed |
| Strict mode | More checks = slower | Balance with actual needs |
| Incremental compile | Full rebuild is slow | Enable incremental in tsconfig |
| Type inference | Complex inference slows build | Explicit types for large objects |
Optimization Tools
Use
tsc --diagnostics to profile compilation. Use --incrementalfor faster rebuilds during development.Common Pitfalls & Solutions
Avoid these TypeScript gotchas
| Pitfall | Problem | Solution |
|---|---|---|
| any everywhere | Defeats type safety | Use strict mode, proper types |
| Type assertion abuse | Hiding real issues | Use type guards instead |
| Circular imports | Can't resolve types | Reorganize, use type-only imports |
| Over-typing | Boilerplate-heavy | Use inference, stick to interfaces |
| Interface mutation | Surprising behavior | Use readonly for immutability |
| Generic confusion | Over-constrained types | Simplify, use extends properly |
| Strict null issues | Everything is nullable | Handle null explicitly |
| Return type mismatch | Forgot return in function | Explicit return types help |
| Property ordering | Object doesn't match type | Use Partial or Omit |
| Index signature abuse | Loses type safety | Use Record or specific keys |
TypeScript with Popular Frameworks
Framework-specific patterns
React with TypeScript
React component typing
// Functional component
interface Props {
title: string;
count?: number;
onCount: (count: number) => void;
}
const Counter: React.FC<Props> = ({
title,
count = 0,
onCount
}) => {
return <div>{title}: {count}</div>;
};
// With useState
import { useState } from 'react';
const [count, setCount] = useState<number>(0);
const [name, setName] = useState('');
// With useCallback
const handleClick = useCallback((e: React.MouseEvent) => {
setCount(c => c + 1);
}, []);
// With useRef
const inputRef = useRef<HTMLInputElement>(null);
// Custom hook
function useCustomHook(initial: string): [
string,
(val: string) => void
] {
const [state, setState] = useState(initial);
return [state, setState];
}Node.js Express Server
Express server with types
import express, { Request, Response } from 'express';
const app = express();
// Typed request/response
app.get('/users/:id', (req: Request<
{ id: string },
any,
any,
any
>, res: Response) => {
const userId = parseInt(req.params.id);
// Handle request
});
// Typed request body
interface CreateUserBody {
name: string;
email: string;
}
app.post('/users', (req: Request<
never,
any,
CreateUserBody
>, res: Response) => {
const { name, email } = req.body;
// Create user
});
// Custom error handling
interface CustomError extends Error {
statusCode?: number;
}
app.use((
err: CustomError,
req: Request,
res: Response,
next: Function
) => {
res.status(err.statusCode || 500)
.json({ error: err.message });
});Migration Checklist
Converting JavaScript to TypeScript
Step-by-Step Migration:
// 1. Setup TypeScript
npm install --save-dev typescript
npx tsc --init
// 2. Rename files
// file.js → file.ts
// 3. Add basic types
// function greet(name) { }
// ↓
function greet(name: string): string {
return `Hello, ${name}!`;
}
// 4. Add interface/types
interface User {
id: number;
name: string;
}
// 5. Update imports
import type { User } from './types';
// 6. Fix type errors
// Use type guards and assertions
// 7. Enable strict mode gradually
// "strict": false → true
// 8. Run linter
npx tsc --noEmitMigration Benefits:
- Catch errors at compile-time
- Better IDE autocomplete
- Self-documenting code via types
- Refactoring with confidence
- Easier team collaboration
- Fewer runtime errors
- Better maintainability
- Compatible with JavaScript
Gradual Adoption
You can enable
allowJs: true in tsconfig to mix JavaScript and TypeScript during migration.Quick Debugging Tips
Debug TypeScript issues
Debugging Techniques
Tools and methods for troubleshooting
// Show inferred type
const x = 42;
type X = typeof x; // number
// Hover over variable in VS Code to see type
const user = { name: 'John', age: 30 };
// Shows: { name: string; age: number; }
// Use conditional to test types
type Test = string extends string ? true : false;
// Hover shows: true
// Check if type is assignable
const test: string = 123; // Error (red squiggly)
// Use keyof to debug object types
interface User {
name: string;
age: number;
}
type Keys = keyof User; // 'name' | 'age'
// Debug mapped types
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]:
() => T[K];
};
type Test = Getters<User>;
// Shows: {
// getName: () => string;
// getAge: () => number;
// }
// Print types in errors (trick)
type Print<T> = T extends never ? 'T is never' : T;
// Use satisfies operator (TS 4.9+)
const config = {
apiUrl: 'https://example.com'
} satisfies Record<string, string>;
// Show type in error message
type ShowType<T> = T & { __type: T };
// Enable diagnostics
// tsc --diagnostics --listFilesOnlyPro Debugging
Hover over variables in VS Code with TypeScript extension to see inferred types. Use strict mode to catch more errors earlier.
Additional Resources
For more TypeScript learning resources, visit our TypeScript Documentation and practice with our TypeScript Exercises.