TypeScript Basics Cheatsheet
A quick reference and learning journey for TypeScript 5.x, covering core concepts, essential syntax, and practical patterns for type-safe JavaScript.
Quick Overview
TypeScript is a superset of JavaScript that adds static types, enabling developers to catch errors early, improve code readability, and build more robust applications, especially at scale. It compiles down to plain JavaScript, making it compatible with any browser, host, or OS. You’d reach for TypeScript to enhance the maintainability and predictability of your JavaScript projects. The content here is based on TypeScript 5.x.
To install the latest stable version globally:
# Install TypeScript globally
npm install -g typescript
Getting Started
Let’s set up a minimal TypeScript project and compile your first .ts file.
-
Initialize a Node.js project:
# Create a new directory and navigate into it mkdir my-ts-app cd my-ts-app # Initialize a new npm project npm init -y -
Install TypeScript locally: It’s recommended to install TypeScript as a development dependency per project to ensure consistent versions across team members.
# Install TypeScript as a dev dependency npm install -D typescript -
Create a TypeScript configuration file (
tsconfig.json): This file specifies compiler options and root files.# Initialize tsconfig.json with default settings npx tsc --initThis command creates a
tsconfig.jsonfile. For a basic setup, you might want to uncomment and adjusttargetandoutDir.// tsconfig.json { "compilerOptions": { "target": "ES2022", // Compile to a modern JavaScript version "module": "Node16", // Specify module code generation "outDir": "./dist", // Output directory for compiled JavaScript "strict": true, // Enable all strict type-checking options (recommended) "esModuleInterop": true, // Enables `import d from 'cjs'` "forceConsistentCasingInFileNames": true // Disallow inconsistent casing }, "include": ["src/**/*"], // Include all .ts files in the src directory "exclude": ["node_modules", "dist"] // Exclude these directories } -
Write your first TypeScript code: Create a
srcdirectory and anindex.tsfile inside it.// src/index.ts function greet(name: string) { console.log(`Hello, ${name.toUpperCase()}!`); } greet("TypeScript"); // Works // greet(123); // Error: Argument of type 'number' is not assignable to parameter of type 'string'. -
Compile and run:
# Compile your TypeScript code npx tsc # Run the compiled JavaScript file node dist/index.jsOutput:
Hello, TYPESCRIPT!
Core Concepts
TypeScript introduces several fundamental concepts to JavaScript for static type-checking and enhanced development experience.
| Concept | Description |
|---|---|
| Static Typing | Assigning types to variables, function parameters, and return values before code execution. This allows the TypeScript compiler to check for type-related errors at compile time. |
| Type Inference | The TypeScript compiler’s ability to automatically deduce the type of a variable or expression when an explicit type is not provided. It infers types based on the assigned value. |
| Interfaces | Define the shape of objects, ensuring they have specific properties and methods. Useful for enforcing contracts for data structures and classes. |
| Type Aliases | Create new names for any type, including primitives, union types, intersection types, and complex object shapes. More flexible than interfaces for non-object types. |
| Classes | TypeScript fully supports ES6 classes with enhancements like access modifiers (public, private, protected), abstract classes, and interfaces implementation. |
| Enums | A way to define a collection of named constants. Useful for representing a set of distinct values (e.g., days of the week, status codes). As of TS 5.0, all enums are union enums. |
| Generics | Write reusable components that work with a variety of types, rather than a single one. This makes components more flexible and type-safe. |
| Type Narrowing | The process by which TypeScript refines a broader type to a more specific one within conditional blocks (e.g., if statements or switch cases). Essential for type guards. |
Essential Syntax
Here are the most common TypeScript syntax elements you’ll encounter.
Basic Types
// Explicit type annotations
let awesomeName: string = "Z2H";
let awesomeAge: number = 30;
let isActiveUser: boolean = true;
// Type inference (TypeScript infers types based on initial assignment)
let greeting = "Hello"; // Type: string
let count = 100; // Type: number
// The 'any' type: Opts out of type checking for a variable. Use sparingly.
let data: any = "Can be anything";
data = 123;
data = false;
// The 'unknown' type: A type-safe counterpart to 'any'. Requires type checking before use.
let safeData: unknown = "Could be anything";
// console.log(safeData.length); // Error: Object is of type 'unknown'.
if (typeof safeData === 'string') {
console.log(safeData.length); // OK, safeData is narrowed to string
}
// The 'void' type: For functions that do not return a value.
function logMessage(message: string): void {
console.log(message);
}
// The 'null' and 'undefined' types
let n: null = null;
let u: undefined = undefined;
// The 'never' type: For functions that never return (e.g., always throw an error or loop infinitely).
function throwError(message: string): never {
throw new Error(message);
}
Arrays
// Array of numbers
let numbers: number[] = [1, 2, 3];
let alsoNumbers: Array<number> = [4, 5, 6];
// Array of strings
let names: string[] = ["Alice", "Bob", "Charlie"];
// Array of mixed types (using a union type)
let mixedArray: (string | number)[] = ["text", 123, "more text"];
Functions
// Function with typed parameters and return type
function add(a: number, b: number): number {
return a + b;
}
// Optional parameter (suffix with ?)
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + " " + lastName;
}
return firstName;
}
// Default parameter
function createGreeting(name: string = "Guest"): string {
return `Welcome, ${name}!`;
}
// Arrow functions with types
const multiply = (a: number, b: number): number => a * b;
// Function type expression
type MathOperation = (x: number, y: number) => number;
const divide: MathOperation = (a, b) => a / b;
Interfaces
Define the shape of objects.
// Define an interface for a User object
interface User {
id: number;
name: string;
email?: string; // Optional property
readonly createdAt: Date; // Readonly property
}
// Implement the interface
const currentUser: User = {
id: 1,
name: "Jane Doe",
createdAt: new Date()
};
// Extending interfaces
interface Admin extends User {
role: "admin";
permissions: string[];
}
const adminUser: Admin = {
id: 2,
name: "Admin User",
createdAt: new Date(),
role: "admin",
permissions: ["read", "write", "delete"]
};
// Interfaces can also describe function types (less common than type aliases for functions)
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = (src, sub) => src.includes(sub);
Type Aliases
Create a new name for a type. Can represent primitives, unions, intersections, or object shapes.
// Alias for a primitive type
type ID = string;
let userId: ID = "abc-123";
// Alias for a union of primitive types
type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "approved";
// Alias for an object shape (similar to interface)
type Point = {
x: number;
y: number;
};
const origin: Point = { x: 0, y: 0 };
// Type aliases with intersections (combining types)
type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge;
const john: Person = { name: "John", age: 30 };
Union & Intersection Types
Combine existing types.
// Union Type: A value can be one of several types.
function printId(id: number | string) {
console.log(`Your ID is: ${id}`);
}
printId(101);
printId("202");
// Intersection Type: A value must have properties of all combined types.
type Draggable = {
drag: () => void;
};
type Resizable = {
resize: () => void;
};
type UIWidget = Draggable & Resizable; // Must have both drag and resize
const myWidget: UIWidget = {
drag: () => console.log("Dragging..."),
resize: () => console.log("Resizing...")
};
Literal Types
Specify exact values a variable can have.
// String literal types
let clickEvent: "click" | "doubleClick" = "click";
// Numeric literal types
let zero: 0 = 0;
// Boolean literal types
let trueLiteral: true = true;
Enums
A set of named constants.
// Numeric Enum
enum Direction {
Up = 1,
Down, // Automatically 2
Left, // Automatically 3
Right // Automatically 4
}
let currentDirection: Direction = Direction.Up;
console.log(currentDirection); // Output: 1
// String Enum (values must be explicitly set)
enum LogLevel {
ERROR = "ERROR",
WARN = "WARN",
INFO = "INFO",
DEBUG = "DEBUG"
}
function log(message: string, level: LogLevel) {
console.log(`${level}: ${message}`);
}
log("Something happened!", LogLevel.INFO); // Output: INFO: Something happened!
Classes
class Animal {
// Public property (default)
public name: string;
// Private property (only accessible within the class)
private age: number;
// Protected property (accessible within the class and its subclasses)
protected species: string;
constructor(name: string, age: number, species: string) {
this.name = name;
this.age = age;
this.species = species;
}
// Public method
makeSound(sound: string): void {
console.log(`${this.name} (${this.species}) says ${sound}`);
}
// Private method (not typically used in basic examples, but possible)
private getAgeInHumanYears(factor: number = 7): number {
return this.age * factor;
}
get ageDescription(): string {
return `${this.name} is ${this.age} years old (approx ${this.getAgeInHumanYears()} human years).`;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, age: number, breed: string) {
super(name, age, "Dog"); // Call parent constructor
this.breed = breed;
}
// Overriding a method
makeSound(sound: string = "Woof!"): void {
console.log(`${this.name} the ${this.breed} (${this.species}) says ${sound}`);
}
// Accessing protected property from parent class
describeSpecies(): string {
return `This is a ${this.species}.`;
}
}
const myDog = new Dog("Buddy", 5, "Golden Retriever");
myDog.makeSound(); // Output: Buddy the Golden Retriever (Dog) says Woof!
console.log(myDog.name); // Output: Buddy
// console.log(myDog.age); // Error: Property 'age' is private
console.log(myDog.ageDescription); // Output: Buddy is 5 years old (approx 35 human years).
Generics
Write flexible, reusable code that works with different types while maintaining type safety.
// Generic function: identity returns whatever type it receives
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // Type of output1 is string
let output2 = identity<number>(100); // Type of output2 is number
// Generic interface
interface GenericBox<T> {
value: T;
}
let stringBox: GenericBox<string> = { value: "hello" };
let numberBox: GenericBox<number> = { value: 123 };
// Generic classes
class GenericPair<K, V> {
constructor(public key: K, public value: V) {}
}
let pair1 = new GenericPair<string, number>("age", 30);
let pair2 = new GenericPair<boolean, string>(true, "active");
Common Patterns
Defining API Response Shapes
Interfaces are excellent for defining the expected structure of data, especially for API responses.
// api.ts
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
interface UserProfile {
id: number;
name: string;
email: string;
posts: Post[]; // Nested interface usage
}
async function fetchUserProfile(userId: number): Promise<UserProfile> {
// In a real app, this would be an actual API call
const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then(res => res.json());
const postsResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`).then(res => res.json());
// Simulate combining data
const userProfile: UserProfile = {
id: userResponse.id,
name: userResponse.name,
email: userResponse.email,
posts: postsResponse.map((post: any) => ({
id: post.id,
title: post.title,
body: post.body,
userId: post.userId
}))
};
return userProfile;
}
// Usage
fetchUserProfile(1).then(profile => {
console.log(`User: ${profile.name}, Email: ${profile.email}`);
console.log(`Posts: ${profile.posts.length}`);
console.log(`First post title: ${profile.posts[0].title}`);
});
Type Guards for Runtime Checks
Type guards allow you to narrow down types within conditional blocks, ensuring type safety at runtime.
// Type guard using 'typeof' for primitives
function processValue(value: string | number) {
if (typeof value === "string") {
// TypeScript knows 'value' is a string here
console.log(`Processing string: ${value.toUpperCase()}`);
} else {
// TypeScript knows 'value' is a number here
console.log(`Processing number: ${value * 2}`);
}
}
processValue("hello");
processValue(10);
// Type guard using 'instanceof' for classes
class Car {
drive() { console.log("Driving car..."); }
}
class Bicycle {
pedal() { console.log("Pedaling bicycle..."); }
}
type Vehicle = Car | Bicycle;
function startVehicle(vehicle: Vehicle) {
if (vehicle instanceof Car) {
// 'vehicle' is narrowed to Car
vehicle.drive();
} else {
// 'vehicle' is narrowed to Bicycle
vehicle.pedal();
}
}
startVehicle(new Car());
startVehicle(new Bicycle());
// User-defined type guards (type predicates)
interface Fish { swim(): void; }
interface Bird { fly(): void; }
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
}
// Example usage
const nemo: Fish = { swim: () => console.log("Nemo is swimming!") };
const tweety: Bird = { fly: () => console.log("Tweety is flying!") };
move(nemo);
move(tweety);
tsconfig.json for Project Configuration
The tsconfig.json file is crucial for telling the TypeScript compiler how to behave.
// A common production-ready tsconfig.json setup
{
"compilerOptions": {
"target": "ES2022", // Specify ECMAScript target version
"module": "Node16", // Specify module code generation
"lib": ["ES2022", "DOM"], // Specify library files to be included in the compilation
"outDir": "./dist", // Redirect output structure to the directory
"rootDir": "./src", // Specify the root directory of input files
"strict": true, // Enable all strict type-checking options (highly recommended)
"esModuleInterop": true, // Enables `import d from 'cjs'`
"forceConsistentCasingInFileNames": true, // Disallow inconsistent casing in file names
"skipLibCheck": true, // Skip type checking of all declaration files (*.d.ts)
"declaration": true, // Generate .d.ts files for emitted JavaScript.
"sourceMap": true // Generate source maps for debugging
},
"include": ["src/**/*.ts"], // Include specific files or patterns
"exclude": ["node_modules", "dist", "**/*.test.ts"] // Exclude specific files or patterns
}
Gotchas & Tips
anyvsunknown: Always preferunknownoveranywhen you don’t know the type of a value.unknownforces you to perform runtime type checks before you can use the value, making your code safer.anycompletely bypasses type checking.- Enable
strict: true: This option intsconfig.jsonenables a suite of strict type-checking options (likenoImplicitAny,strictNullChecks,strictFunctionTypes, etc.) that catch a vast number of common errors. It’s the recommended baseline for new projects. - Type Assertions (
askeyword): Useas Typeto tell TypeScript “I know better, trust me on this type.” Be cautious, as it disables type checking for that specific assertion and can lead to runtime errors if you’re wrong.let someValue: unknown = "this is a string"; let strLength: number = (someValue as string).length; // Asserting 'someValue' as string - Declaration Merging: Interfaces, unlike type aliases, can be declared multiple times, and TypeScript will merge their definitions. This is often used when extending types from external libraries or for modular augmentation.
// In library.d.ts interface MyConfig { optionA: string; } // In your app.ts interface MyConfig { optionB: number; // Merges with the above } const config: MyConfig = { optionA: "foo", optionB: 123 }; - Version Specificity: TypeScript evolves rapidly. Features like ECMAScript decorators and
consttype parameters were significant additions in TypeScript 5.0 and beyond. Always refer to the official documentation for features introduced in specific versions.
Next Steps
- Official TypeScript Handbook: The comprehensive guide to TypeScript, maintained by the TypeScript team. It’s an invaluable resource. https://www.typescriptlang.org/docs/handbook/intro.html
- TypeScript Playground: Experiment with TypeScript directly in your browser and see the compiled JavaScript output. https://www.typescriptlang.org/play
- Read the
tsconfig.jsondocumentation: Deep dive into all available compiler options to fine-tune your project. https://www.typescriptlang.org/tsconfig
Source: z2h.fyi/cheatsheets/typescript-basics — Zero to Hero cheatsheets for developers.