function add(a: number, b: number): number {
return a + b;
}
// The above TypeScript function is translated directly to JavaScript.
// In tsconfig.json, set "strict" to enforce strict type checking
{
"compilerOptions": {
"strict": true
}
}
// TypeScript will erase types during JavaScript compilation
function greet(name: string): string {
return `Hello, ${name}`;
}
// Transpiles to: function greet(name) { return "Hello, " + name; }
interface Point { x: number; y: number; }
const p = { x: 10, y: 20 };
function logPoint(pt: Point) {
console.log(pt.x, pt.y);
}
logPoint(p); // Works because of structural typing
function logValue(val: any) {
console.log(val);
}
// Avoid `any`. Use specific types like:
function logValue(val: string | number) {
console.log(val);
}
// Hovering over variables in TypeScript-enabled editors like VSCode shows inferred types
const message = "Hello"; // Hover shows: const message: string
type Direction = "left" | "right";
function move(dir: Direction) {
console.log(dir);
}
move("left"); // Valid
move("up"); // Error: "up" is not assignable to Direction
interface Dog { bark(): void; }
const Dog = { species: 'Canine' };
// 'Dog' in type space is different from 'Dog' in value space
let myVar = "string" as any; // Type assertion, try to avoid
let myVar: string = "string"; // Prefer type annotation
let s: string = new String("hello"); // Avoid
let s: string = "hello"; // Prefer primitive types
interface Animal { name: string; }
const cat: Animal = { name: 'Lince', age: 5 }; // Error: 'age' is excess property
// Avoid by using a variable first
const lince = { name: 'Lince', age: 5 };
const cat: Animal = lince; // OK
type Greet = (name: string) => string;
const greet: Greet = (name) => `Hello, ${name}`; // Applies type to the whole function
interface Dog { breed: string; }
type Cat = { breed: string; }
// Use `interface` for extending
interface Husky extends Dog { color: string; }
interface Point { readonly x: number; readonly y: number; }
const p: Point = { x: 10, y: 20 };
p.x = 30; // Error: Cannot assign to 'x' because it is readonly
type Person = { firstName: string; lastName: string };
type WithBirthDate = Person & { birthDate: Date };
// Use generics and intersections to avoid duplication in types.
// Avoid general index signatures
interface Inventory {
[key: string]: number; // imprecise
}
// Prefer specific fields or use mapped types
interface ExactInventory {
apples: number;
bananas: number;
}
interface Team {
[index: number]: string; // Avoid numeric index signatures
}
const team: Team = { 0: "Alice", 1: "Bob" }; // Replace with an array if possible
const players: string[] = ["Alice", "Bob"]; // Prefer arrays
let name = "John"; // TypeScript infers `name` is of type `string`, no need for annotation.
let id: string | number; // Bad practice, split types into separate variables.
let stringId: string = "123";
let numericId: number = 123;
let x = 10; // x is inferred as number
let y = x + 5; // TypeScript infers based on context
const point = { x: 10, y: 20 }; // Declare object all at once instead of incrementally.
function printValue(val: string | number) {
if (typeof val === "string") {
console.log(val.toUpperCase()); // Type narrowed to string
} else {
console.log(val + 1); // Type narrowed to number
}
}
type ID = string | number;
let id: ID = "123"; // Consistent use of the alias across the codebase
const add = (a: number, b: number) => a + b; // TypeScript infers the return type based on context
let person = { name: "John" }; // Initially inferred as { name: string }
person.age = 30; // Error: 'age' does not exist on the inferred type
// Use map to let types flow through
const numbers = [1, 2, 3].map(n => n * 2); // Inferred as number[]
async function fetchData() {
const response = await fetch("url");
const data = await response.json();
}
class Calculator {
constructor(private value: number) {}
add(n: number) { return new Calculator(this.value + n); }
}
const calc = new Calculator(5).add(10); // Inference works through class methods
type SuccessResponse = { status: "success"; data: string };
type ErrorResponse = { status: "error"; error: string };
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
if (response.status === "success") {
console.log(response.data);
}
}
function double(input: number | string): number {
return typeof input === "number" ? input * 2 : parseFloat(input) * 2;
}
/**
* Concatenates two strings
* @param a - The first string
* @param b - The second string
*/
function concat(a: string, b: string): string {
return a + b;
}
function getLength(input: string | null): number {
if (input === null) return 0;
return input.length;
}
function processInput(input: string | null): string {
if (input === null) return "default";
return input.trim();
}
interface Cat { type: "cat"; meow(): void; }
interface Dog { type: "dog"; bark(): void; }
type Animal = Cat | Dog;
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
function sendRequest(method: HTTPMethod, url: string) {
console.log(`Sending ${method} request to ${url}`);
}
type InvalidDate = { type: "InvalidDate" };
type ValidDate = { type: "ValidDate"; date: Date };
type DateValue = InvalidDate | ValidDate;
interface UserProfile {
name: string;
age?: number; // Use optional properties sparingly
}
function drawRect({ x, y }: { x: number; y: number }, { width, height }: { width: number; height: number }) {
console.log(`Drawing rectangle at (${x}, ${y}) with dimensions ${width}x${height}`);
}
type User = { name: string; email?: string }; // Unified type for users with and without email
type User = { id: string; name: string }; // Imprecise but flexible
// Avoid overly accurate types that limit future flexibility.
type OrderStatus = "pending" | "completed"; // Use domain-specific language for clarity
// Avoid creating types just based on limited or specific data
type LimitedType = { property: number }; // Avoid basing types on small datasets
function process(value: any) {
const str: string = value as string; // Scope `any` to just where it's needed
}
function process(values: any[]) {
values.forEach(value => console.log(value)); // More precise than plain `any`
}
function parseJSON(json: string): any {
return JSON.parse(json);
}
function safelyParseJSON<T>(json: string): T {
return parseJSON(json) as T; // Unsafe assertion hidden in a well-typed function
}
function handleValue(value: unknown) {
if (typeof value === "string") {
console.log(value.toUpperCase());
}
}
interface Person {
name: string;
}
const person: Person = { name: "Alice" };
(person as any).age = 30; // Avoid monkey patching like this
// Avoid soundness traps like unchecked object lookups
type Data = { [key: string]: string };
const data: Data = { name: "John" };
console.log(data["nonExistentKey"]); // Might lead to undefined at runtime
// Use tools like type-coverage to track your project's type safety
// Command example:
$ npx type-coverage
function identity<T>(arg: T): T {
return arg;
}
// Generic functions work like functions for types
// Avoid adding extra type parameters that don't add value
function logLength<T>(arg: T[]): void {
console.log(arg.length); // T[] already indicates an array, no need for extra type
}
type IsString<T> = T extends string ? true : false;
// Conditional types are more flexible than overloading
type TypeName<T> = T extends string ? "string" : "other";
type Distributed = TypeName<string | number>; // Distributes to "string" | "other"
type Path = `${string}/${string}`;
const path: Path = "user/profile"; // Template literal type for modeling string patterns
// You can use libraries like tsd to write tests for your types
$ npx tsd
type User = { name: string; age: number };
type Admin = User & { role: string };
This combination will display in editors as { name: string; age: number; role: string }
, allowing you to check type composition visually.
type TupleLength<T extends any[]> = T extends { length: infer L } ? L : never;
// Use tail-recursion to avoid type expansion limits
// Use code generation tools for complex types instead of managing them manually
type Shape = { type: 'circle' | 'square'; };
function handleShape(shape: Shape) {
if (shape.type === 'circle') {
console.log('Circle');
} else if (shape.type === 'square') {
console.log('Square');
} else {
const _exhaustive: never = shape; // Error if not exhaustive
}
}
const myObject = { a: 1, b: 2 };
Object.entries(myObject).forEach(([key, value]) => {
console.log(key, value);
});
type RecordType = Record<string, number>;
const data: RecordType = { apples: 10, bananas: 5 };
function sum(...args: [number, number, number]): number {
return args.reduce((a, b) => a + b, 0);
}
type EitherOr = { a: string; b?: never } | { a?: never; b: string };
type UserID = string & { readonly brand: unique symbol };
function createUser(id: UserID) {
// UserID is nominally typed and cannot be confused with other strings
}
{
"devDependencies": {
"typescript": "^4.0.0",
"@types/node": "^14.0.0"
}
}
- TypeScript version
- Library version
- @types version
// Export all types that are part of a public API
export interface User {
id: string;
name: string;
}
/**
* Adds two numbers.
* @param a - The first number.
* @param b - The second number.
* @returns The sum of a and b.
*/
function add(a: number, b: number): number {
return a + b;
}
class MyClass {
myMethod() {
[1, 2, 3].forEach(function (this: MyClass, num) {
console.log(this); // Now `this` is typed as MyClass
}, this);
}
}
interface ExternalData {
value: number;
}
interface LocalData {
value: number;
}
// Mirror types avoid unnecessary coupling to external libraries.
declare module 'my-module' {
interface MyType {
newProperty: string;
}
}
// Augments existing types in a module.
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2); // Use native ECMAScript methods when possible.
{
"compilerOptions": {
"sourceMap": true
}
}
// Ensure source maps are enabled for debugging TypeScript in its original form.
function isString(value: any): value is string {
return typeof value === "string";
}
// Use type predicates to reconstruct types at runtime.
const element = document.getElementById("myElement"); // HTMLElement
declare const process: {
env: {
NODE_ENV: "development" | "production";
};
};
// Accurately model your environment to prevent errors.
// TypeScript handles types, but you still need unit tests for behavior.
function add(a: number, b: number): number {
return a + b;
}
{
"compilerOptions": {
"incremental": true
}
}
// Use incremental builds to improve compiler performance.
// Use modern JavaScript features, like async/await, arrow functions, and destructuring
const fetchData = async (url: string) => {
const response = await fetch(url);
const data = await response.json();
console.log(data);
};
// JavaScript file with @ts-check enabled for type checking
// @ts-check
/**
* Adds two numbers
* @param {number} a
* @param {number} b
* @returns {number}
*/
function add(a, b) {
return a + b;
}
// tsconfig.json configuration to allow JS files in a TypeScript project
{
"compilerOptions": {
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*.ts", "src/**/*.js"]
}
// Start migrating TypeScript in small modules and move upwards
// Example: Start with a utility module
export function capitalize(str: string): string {
return str[0].toUpperCase() + str.slice(1);
}
// Enabling noImplicitAny in tsconfig.json
{
"compilerOptions": {
"noImplicitAny": true
}
}