DevTools

Cheatsheet TypeScript

JavaScript com tipagem estática

Back to languages
TypeScript
94 cards found
Categories:
Versions:

Basic Types


12 cards
Primitive Types
let name: string = "Anna";
let age: number = 30;
let active: boolean = true;
let nothing: null = null;
let indef: undefined = undefined;

The primitive types are string, number and boolean, plus null and undefined. The : type annotation is optional when the value allows inference.

Union Types
let id: string | number;
id = "abc";
id = 123;

function format(v: string | number): string {
    return String(v);
}

A union type allows a value to be of several types, separated by |. Inside the function you can only use operations common to all of them; for specific operations do narrowing.

const assertion (the const)
const config = {
    host: "localhost",
    port: 3000,
} the const;

// config.port = 4000; // ERROR: readonly
type Doors = typeof config.port; // 3000

the const makes the value deeply immutable and converts the types into literals (3000 instead of number). It's useful for constants and for creating types from data.

Arrays and Tuples
let nums: number[] = [1, 2, 3];
let texts: Array<string> = ["a", "b"];

let tuplo: [string, number] = ["Anna", 30];
tuplo[0]; // string

Arrays are typed with type[] or Array<type>. A tuple is an array with a fixed size and fixed types per position, useful for pairs of values.

Literal Types
let dir: "left" | "right";
dir = "left";

type Status = "active" | "inactive";
let s: Status = "active";

A literal type restricts the variable to exact values. Combined with unions it creates closed sets of options, safer than string. Ideal for states and settings.

typeof and instanceof
function area(v: number | Date) {
    if (typeof v === "number") {
        return v * v; // number
    }
    return v.getTime(); // Date
}

The typeof (for primitives) and instanceof (for classes) operators do narrowing: inside the if TypeScript narrows the type automatically, allowing specific operations.

any, unknown, never, void
let x: any = "any";   // no checking
let y: unknown = 42;       // safe, requires a check

function error(): never {
    throw new Error("failure");
}
function log(): void { }   // no return

any disables checking (avoid it). unknown accepts any value but forces you to check before using. never is for functions that never return and void for those that don't return a value.

Optional and null
interface User {
    name: string;
    email?: string; // optional
}
let value: string | null = null;
let def: string = value ?? "default";

The ? marks an optional property (can be undefined). The ?? operator (nullish coalescing) returns the right-hand value if the left-hand one is null or undefined.

Typed Objects
let point: { x: number; y: number } = {
    x: 1,
    y: 2,
};

let user: { name: string; age?: number };

You can type objects directly with { field: type }. Each property is required unless marked with ?. For reused shapes, prefer an interface or type.

Type Inference
let name = "Anna";        // string
let nums = [1, 2, 3];    // number[]
const obj = { x: 1, y: 2 };

// name = 10; // ERROR: not a number

TypeScript infers the type from the initial value, so many annotations are unnecessary. const variables have more specific types. Let the compiler infer whenever possible.

Type Assertion (the)
let el = document.getElementById("app") as HTMLDivElement;
let n = ("123" the string).length;

let input = document.querySelector("input")!;

A type assertion with the tells the compiler to treat a value the a specific type, without checking. Use it only when you know more than TypeScript. It doesn't convert the value at runtime.

bigint and symbol
let big: bigint = 9007199254740991n;
let id: symbol = Symbol("id");

let key: unique symbol = Symbol("key");

bigint represents arbitrarily large integers (suffix n) and symbol creates unique identifiers. unique symbol is a unique type associated with a constant.

Functions


10 cards
Typed Function
function add(a: number, b: number): number {
    return a + b;
}

const double = (x: number): number => x * 2;

Type the parameters and the return of a function. The return is often inferred, but annotating it documents the contract. Arrow functions follow the same rule.

Function Type
type Callback = (data: string) => void;

function fetchDados(cb: Callback) {
    cb("result");
}
fetchDados((d) => console.log(d));

You can define a function type with type, describing parameters and return. It's reusable across several signatures and makes callbacks more readable and safer.

void vs undefined Return
function log(msg: string): void {
    console.log(msg);
}

function find(id: number): string | undefined {
    return undefined;
}

A void return means the function doesn't return anything useful. undefined the a return type, on the other hand, indicates it can explicitly return that value. They are distinct concepts.

Optional and Default Parameters
function greet(name: string, title?: string) {
    return title ? `${title} ${name}` : name;
}

function connect(host: string = "localhost") { }

An optional parameter is marked with ? and can be undefined. A parameter with a default value uses = value and is already optional at the call. Optional ones must come after required ones.

Typed Arrow Function
const multiply: (a: number, b: number) => number =
    (a, b) => a * b;

const items = [1, 2, 3].map((n: number) => n * 2);

In arrow functions, the type is declared on the variable with the syntax (params) => return. The implementation parameters are inferred. In .map() the item type is inferred from the array.

Simple Generic Function
function identity<T>(value: T): T {
    return value;
}
identity<string>("hello");
identity(42); // T inferred the number

A generic function uses <T> to accept any type while keeping the relationship between input and output. The type can be explicit or inferred. It's the foundation of reusable programming.

Rest Parameters
function sum(...nums: number[]): number {
    return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10

The rest parameter ...name: type[] accepts a variable number of arguments, treated the a typed array. It must be the last parameter of the function.

Typed Callbacks
type Handler = (event: string, code: number) => void;

function register(h: Handler) {
    h("click", 200);
}
register((ev, cod) => console.log(ev, cod));

When passing callbacks, type them so the parameters are inferred at the usage. This way the compiler checks the arguments and the return, avoiding errors in higher-order functions.

Function Overloads
function parse(v: string): number;
function parse(v: number): string;
function parse(v: string | number) {
    return typeof v === "string"
        ? parseInt(v) : String(v);
}

Overloads define several signatures for the same function. The first lines declare the cases and the last one is the implementation. The compiler picks the right signature based on the argument.

Typed this
interface Element {
    name: string;
    show(this: Element): void;
}

function show(this: Element) {
    console.log(this.name);
}

TypeScript allows you to type this the a pseudo-parameter in the first position. It doesn't count the a real argument, but it helps the compiler check the call context.

Interfaces e Types


11 cards
Basic Interface
interface Person {
    name: string;
    age: number;
    email?: string; // optional
}
const p: Person = { name: "Anna", age: 30 };

An interface defines the shape of an object: property names and types. The ? marks optional fields. It's the most common way to type objects in TypeScript.

Index Signature
interface Dict {
    [key: string]: number;
}
const grades: Dict = { math: 95, port: 88 };

type Map = Record<string, number>;

An index signature [key: string]: type describes objects with dynamic keys. The utility Record<string, type> is the modern, concise way to do the same.

Declaration Merging
interface Window {
    title: string;
}
interface Window {
    width: number;
}
// Window has title AND width

Declaration merging automatically merges interfaces with the same name into one. It's useful for extending library types. It only works with interface, not with type.

type alias
type Point = {
    x: number;
    y: number;
};

type ID = string | number;

type creates an alias for any type, including objects, unions and primitives. Unlike interface, it accepts unions and compound types. For simple objects they are nearly equivalent.

Readonly
interface Config {
    readonly host: string;
    readonly port: number;
}
const cfg: Config = { host: "x", port: 80 };
// cfg.host = "y"; // ERROR

The readonly modifier makes a property immutable after creation. Trying to reassign causes a compile error. For arrays use readonly type[] or ReadonlyArray.

Multiple Extends
interface HasId { id: number; }
interface HasDate { createdAt: Date; }

interface Entity extends HasId, HasDate {
    name: string;
}

An interface can extend several interfaces at once, separated by commas. The resulting interface inherits all fields. It's a clean way to compose reusable contracts.

interface vs type
// interface: extensible, merging
interface A { x: number; }
interface A { y: number; } // merge

// type: unions, mapped
type B = string | number;

Use interface for public object contracts (allows declaration merging and extends) and type for unions, tuples and derived types. Both work for objects.

Interface for a Function
interface Comparator {
    (a: number, b: number): number;
}

const cmp: Comparator = (a, b) => a - b;

An interface can describe a function signature with (params): return. The implementation parameters are inferred. It's an alternative to a function type.

Type Derived from an Object
const config = {
    host: "localhost",
    port: 3000,
} the const;

type Config = typeof config;

The typeof operator in type position creates a type from an existing value. Combined with the const, it generates precise literal types. It avoids duplicating the definition.

Extends
interface Animal {
    name: string;
}
interface Dog extends Animal {
    raca: string;
}
const c: Dog = { name: "Rex", raca: "Labrador" };

extends creates an interface that inherits the fields of another and adds its own. The child interface includes all properties of the parent. It's the way to compose contracts.

Intersection Types (&)
type Name = { name: string };
type Age = { age: number };

type Person = Name & Age;
const p: Person = { name: "Anna", age: 30 };

An intersection type combines several types with &, requiring the properties of all of them. It's the opposite of union: instead of "one or the other", it's "all together".

Classes


10 cards
Class with Types
class Person {
    name: string;
    private age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

In a class, declare the fields with their types before the constructor. TypeScript checks the assignments to this. All required fields must be initialized.

Implements
interface Serializable {
    toJSON(): string;
}

class User implements Serializable {
    toJSON() { return "{}"; }
}

implements ensures that a class fulfills the contract of an interface. If a method or property is missing, the compiler warns. A class can implement several interfaces.

override
class Base {
    method() { return "base"; }
}
class Child extends Base {
    override method() { return "child"; }
}

The override keyword explicitly marks that a method replaces the parent's. With the noImplicitOverride option in tsconfig, it becomes mandatory and prevents renaming errors.

Access Modifiers
class Account {
    public holder: string;
    private balance: number = 0;
    protected id: number = 0;
    readonly bank: string = "CGD";
}

public is visible everywhere, private only in the class, protected in the class and subclasses, and readonly prevents reassignment. By default members are public.

Abstract Class
abstract class Shape {
    abstract area(): number;
    description() { return "shape"; }
}
// new Shape(); // ERROR

An abstract class cannot be instantiated directly and serves the a base. It can have abstract methods (with no body, marked with abstract) that subclasses are required to implement.

Generic Class
class Stack<T> {
    private items: T[] = [];
    push(item: T) { this.items.push(item); }
    pop(): T | undefined { return this.items.pop(); }
}
const p = new Stack<number>();

A generic class uses <T> after the name to parameterize the types of its members. The type is set when instantiating with new Class<type>() or inferred.

Parameter Properties
class Point {
    constructor(
        public x: number,
        public y: number
    ) {}
}
const p = new Point(1, 2);

A parameter property declares and assigns a field directly in the constructor, attaching a modifier (public, private, readonly) to the parameter. It greatly reduces boilerplate code.

Getters and Setters
class Circle {
    constructor(private radius: number) {}
    get area(): number {
        return Math.PI * this.radius ** 2;
    }
    set newRadius(r: number) { this.radius = r; }
}

Getters (get) and setters (set) define controlled access to a property. They are accessed like normal fields, but allow validation or computation behind the scenes.

Inheritance (extends)
class Animal {
    constructor(public name: string) {}
    speak() { return "..."; }
}
class Dog extends Animal {
    speak() { return "Au!"; }
}

Inheritance with extends allows a child class to reuse and override methods from the parent. The child's constructor must call super() if the parent has one.

Static
class MathUtils {
    static readonly PI = 3.14159;
    static double(n: number): number {
        return n * 2;
    }
}
MathUtils.double(5);

static members belong to the class, not the instance, and are accessed with Class.member. They can be methods, fields or readonly. Useful for utilities and constants.

Generics


10 cards
Generic Function
function first<T>(arr: T[]): T | undefined {
    return arr[0];
}
first([1, 2, 3]);    // number
first(["a", "b"]);   // string

A generic function declares <T> before the parameters. The type is inferred from the arguments and keeps the relationship between input and output, without resorting to any.

Generics with Default
interface Response<T = unknown> {
    data: T;
    error?: string;
}
const r: Response<string[]> = { data: [] };
const s: Response = { data: null };

A generic parameter can have a default value with T = type. If none is given, the default is used. Useful for making types optional without losing safety.

Constraint with keyof
function sort<T, K extends keyof T>(
    items: T[], key: K
): T[] {
    return [...items].sort(
        (a, b) => (a[key] > b[key] ? 1 : -1)
    );
}

Combining K extends keyof T with arrays enables safe functions that operate on a specific property. The compiler verifies that the key exists on T.

Generic Interface
interface Box<T> {
    value: T;
    open(): T;
}
const c: Box<number> = {
    value: 42,
    open: () => 42,
};

A generic interface uses <T> to create reusable types. The parameter is supplied when using it (Box<number>). Very common in API responses and containers.

Multiple Parameters
function join<A, B>(a: A, b: B): A & B {
    return { ...a, ...b };
}
const r = join({ x: 1 }, { y: 2 });
// r: { x: number } & { y: number }

A function can have several type parameters, separated by commas. Each one is inferred independently. The return A & B combines the shapes of the two objects.

Generics in Promises
async function fetchUser(id: number): Promise<User> {
    const r = await fetch(`/api/${id}`);
    return r.json();
}
const u = await fetchUser(1); // User

Promise<T> is generic: it indicates the type of the resolved value. By annotating the return of async functions, await yields the correct type. Essential for type-safe asynchronous code.

Constraints (extends)
function largest<T extends { length: number }>(
    a: T, b: T
): T {
    return a.length > b.length ? a : b;
}

A constraint T extends Type restricts the generic to types that have a certain shape. Here it guarantees that T has length, allowing that property to be accessed safely.

Indexed Access (T[K])
interface User {
    name: string;
    age: number;
}
type Name = User["name"];  // string
type Value = User[keyof User]; // string | number

An indexed access T[K] extracts the type of a property from another type. Combined with keyof, it builds unions of values. Very useful in generic utilities.

keyof
function get<T, K extends keyof T>(
    obj: T, key: K
): T[K] {
    return obj[key];
}
get({ name: "Anna" }, "name"); // string

keyof T produces a union of the keys of T. Combined with K extends keyof T, it guarantees the key exists. The return type T[K] is the type of that property.

Generic Arrow in .tsx
// in .ts
const id = <T>(x: T): T => x;

// in .tsx (React) use a trailing comma
const id2 = <T,>(x: T): T => x;

In .tsx files, the generic arrow <T> gets confused with JSX. Add a comma <T,> or use <T extends unknown> so the parser recognizes the generic.

Utility Types


11 cards
Partial / Required
type Partial = Partial<Person>;
// all fields optional

type Complete = Required<Partial>;
// all required

Partial<T> makes all properties optional, useful for update functions. Required<T> does the opposite, removing the ? and making everything required.

ReturnType
function add(a: number, b: number) {
    return a + b;
}
type R = ReturnType<typeof add>; // number

ReturnType<T> extracts the return type of a function. It's used with typeof func to get the function's type. It avoids duplicating the return type manually.

Awaited
type A = Awaited<Promise<string>>;   // string
type B = Awaited<Promise<number[]>>; // number[]
type C = Awaited<boolean>;           // boolean

Awaited<T> "unwraps" the resolved type of a Promise, just like the await operator. It works recursively on nested promises. Useful for typing asynchronous results.

Pick / Omit
type Name = Pick<Person, "name">;
type SemEmail = Omit<Person, "email">;

type Basico = Pick<User, "id" | "name">;

Pick<T, K> selects only the given properties and Omit<T, K> excludes them, keeping the rest. They are ideal for creating variants of a type without rewriting it.

Extract / Exclude
type T = "a" | "b" | "c";
type AB = Extract<T, "a" | "b">; // "a" | "b"
type C = Exclude<T, "a" | "b">;  // "c"

Extract<T, U> keeps from a union only the types assignable to U; Exclude<T, U> removes them. They are useful for filtering members of literal unions.

InstanceType
class User {
    name = "Anna";
}
type U = InstanceType<typeof User>; // User

type Ctor = new () => User;

InstanceType<T> extracts the instance type of a constructor. It's used with typeof Class. It's common in generic factories that receive constructors and return instances.

Record
type Grades = Record<string, number>;
const n: Grades = { math: 95, port: 88 };

type Map = Record<"a" | "b", boolean>;

Record<K, V> creates an object whose keys are of type K and values of type V. More precise than an index signature when the keys are known.

NonNullable
type T = string | null | undefined;
type NN = NonNullable<T>; // string

function usar(v: string | undefined) {
    const s: NonNullable<typeof v> = v ?? "";
}

NonNullable<T> removes null and undefined from a type. It's useful to guarantee a value is present after a check, without rewriting the type.

Uppercase / Capitalize
type A = Uppercase<"ana">;    // "ANA"
type B = Lowercase<"ANA">;    // "ana"
type C = Capitalize<"ana">;   // "Anna"
type D = Uncapitalize<"Anna">; // "ana"

The template literal utility types transform strings at the type level: Uppercase, Lowercase, Capitalize and Uncapitalize. They only operate on string literal types.

Readonly<T>
type Immutable = Readonly<Person>;

const p: Immutable = { name: "Anna", age: 30 };
// p.age = 31; // ERROR: readonly

Readonly<T> makes all properties of T immutable. Any reassignment causes an error. It is shallow (doesn't affect nested objects); for arrays use ReadonlyArray.

Parameters
function add(a: number, b: number) {
    return a + b;
}
type P = Parameters<typeof add>;
// [a: number, b: number]

Parameters<T> extracts a function's parameter types the a tuple. It's used with typeof. Handy for reusing the signature of existing functions.

Advanced Types


11 cards
Type Guards
function process(v: string | number) {
    if (typeof v === "string") {
        return v.toUpperCase();
    }
    return v.toFixed(2);
}

Type guards use typeof, instanceof or checks to narrow (narrowing) a union. Inside each branch TypeScript knows the exact type.

Template Literal Types
type Event = `on${Capitalize<"click" | "focus">}`;
// "onClick" | "onFocus"

type Route = `/${string}`;

Template literal types create string types from template literals. Combined with unions they generate all combinations. Useful for event names, routes and patterns.

Exhaustiveness (never)
function area(f: Shape): number {
    switch (f.type) {
        case "circle": return Math.PI * f.radius ** 2;
        case "square": return f.side ** 2;
        default:
            const _ex: never = f;
            return _ex;
    }
}

Assigning the default case to a never variable guarantees exhaustiveness: if you add a new case to the union without handling it, the compiler warns. It makes the switch future-proof.

Discriminated Unions
type Shape =
    | { type: "circle"; radius: number }
    | { type: "square"; side: number };

function area(f: Shape) {
    switch (f.type) {
        case "circle": return Math.PI * f.radius ** 2;
        case "square": return f.side ** 2;
    }
}

A discriminated union is a union of objects with a common literal field (the discriminator, here type). A switch over it performs automatic, safe narrowing.

infer
type Unpack<T> = T extends Promise<infer U>
    ? U : T;

type R = Unpack<Promise<string>>; // string

The infer keyword declares a type variable inside a conditional, letting TypeScript deduce it. Here it extracts the inner type of a Promise.

Recursive Types
type Json =
    | string
    | number
    | boolean
    | null
    | Json[]
    | { [key: string]: Json };

A recursive type references itself, describing nested structures like JSON or trees. TypeScript resolves the recursion automatically. Useful for hierarchical data.

Mapped Types
type Optional<T> = {
    [K in keyof T]?: T[K];
};

type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

A mapped type creates a new type by iterating over the keys of another with [K in keyof T]. It lets you transform all properties programmatically. It's the foundation of the utility types.

Narrowing with in
type Dog = { bark(): void };
type Cat = { meow(): void };

function speak(a: Dog | Cat) {
    if ("bark" in a) a.bark();
    else a.meow();
}

The in operator checks whether a property exists on the object and performs narrowing of the union. It's an alternative to typeof when the types are distinguished by present fields.

Mapped with Modifiers
type SoLeitura<T> = {
    +readonly [K in keyof T]: T[K];
};
type SemOpcionais<T> = {
    [K in keyof T]-?: T[K];
};

In a mapped type, the + and - prefixes add or remove modifiers. +readonly makes it immutable and -? removes optionality. They give fine control over the transformation.

Conditional Types
type IsString<T> = T extends string
    ? "yes" : "not";

type A = IsString<string>; // "yes"
type B = IsString<number>; // "not"

A conditional type chooses between two types with the syntax T extends U ? X : Y. It's the "if" of the type system. It combines with infer to extract types.

Type Predicates (is)
function isString(v: unknown): v is string {
    return typeof v === "string";
}

if (isString(value)) {
    value.toUpperCase(); // string
}

A type predicate param is Type in the return creates a custom guard. When the function returns true, TypeScript narrows the type in that branch. Ideal for reusable checks.

Modules and Enums


9 cards
Export / Import
// module.ts
export const PI = 3.14;
export function add(a: number, b: number) { }
export default class App { }

// uso.ts
import App, { PI, add } from "./module";

export makes values available and import brings them in from another module. export default defines the main export, imported without braces. Named ones use { }.

Numeric vs String Enum
enum Num { A, B, C } // 0, 1, 2
enum Str { A = "a", B = "b" }

let n: Num = Num.A; // 0
let s: Str = Str.A; // "a"

Numeric enums auto-increment from 0 and allow reverse mapping. String enums are more readable in logs and debugging. String ones are generally safer.

Re-export and Barrel
// index.ts (barrel)
export { User } from "./user";
export { Post } from "./post";
export * from "./utils";

import { User, Post } from "./index";

A barrel file (usually index.ts) re-exports several modules with export { } or export *. It simplifies imports, allowing you to import everything from a single place.

Type-only Import/Export
import type { User } from "./types";
export type { Config } from "./config";

import { type Options, create } from "./lib";

import type imports only types, which are removed at compile time. It avoids unnecessary runtime imports and conflicts. With verbatimModuleSyntax in tsconfig it becomes mandatory.

Namespace
namespace Utils {
    export function format(s: string) {
        return s.trim();
    }
}
Utils.format("hello");

A namespace groups related code under a name. It's an old way of organizing; today ES modules (import/export) are preferred. Still useful in legacy or global code.

Enums
enum Direction {
    Cima = "UP",
    Baixo = "DOWN",
}
let d: Direction = Direction.Cima;

An enum defines a set of named constants. It can have string or numeric values. It's accessed with Enum.Member. In modern code, unions of literals are often preferred.

Declaration Files (.d.ts)
// global.d.ts
declare const API_URL: string;

interface Window {
    myLib: string;
}

.d.ts files contain only type declarations, with no executable code. They serve to type libraries without types or global variables. declare announces existence without implementing.

Const Enum
const enum Status {
    Active = 1,
    Inactive = 0,
}
let s = Status.Active; // inlined the 1

A const enum is fully inlined at compile time, generating no runtime object. It's more efficient, but cannot be inspected dynamically. Use it in moderation.

declare module (wildcard)
declare module "*.css" {
    const classes: Record<string, string>;
    export default classes;
}

declare module "lib-without-types";

declare module with a pattern (e.g. "*.css") types non-TS imports, like CSS or images. It also declares modules without types to avoid errors. It goes in a .d.ts.

Tips and Setup


10 cards
Essential tsconfig.json
{
    "compilerOptions": {
        "strict": true,
        "target": "ES2022",
        "module": "ESNext",
        "outDir": "./dist",
        "esModuleInterop": true
    }
}

tsconfig.json configures the compiler. strict: true enables all strict checks. target sets the version of the generated JS and outDir the output destination. Create it with tsc --init.

strict and Useful Flags
{
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noImplicitOverride": true
}

strict enables strictNullChecks, noImplicitAny and others. Flags like noUnusedLocals and noImplicitOverride reinforce quality. Enable the many the the project allows.

Common Errors
// using == instead of ===
// forgetting await on a Promise
// any instead of unknown
// mutating a readonly
// comparing objects with ===

Frequent errors: using any instead of unknown, forgetting await, comparing objects with === (compares reference) and ignoring readonly. strict catches most of them.

CLI: Commands
npx tsc --init      // creates tsconfig
npx tsc --watch     // recompiles on save
npx tsc --noEmit    // only checks types
npx tsc app.ts      // compiles a file

The tsc compiler compiles and checks types. --watch recompiles automatically and --noEmit only validates without generating files, useful in CI. Install it with npm i -D typescript.

Optional Chaining (?.)
const city = user?.address?.city;
const method = obj.method?.();
const item = list?.[0];

Optional chaining ?. safely accesses nested properties: if any value is null or undefined, it returns undefined instead of throwing an error. It works on methods and indexes.

Best Practices
// avoid any (use unknown)
// enable strict: true
// interfaces for objects
// type for unions/aliases
// narrowing instead of the

Avoid any (prefer unknown), enable strict, use interface for objects and type for unions. Prefer narrowing over the and let the compiler infer whenever possible.

Non-null Assertion (!)
let el = document.getElementById("app")!;
el.textContent = "Hello";

// tells TS: "it is not null/undefined"

The ! operator (non-null assertion) assures the compiler that a value is not null or undefined. It does no runtime check; use it only when you are certain.

Definite Assignment (!)
class User {
    name!: string; // will be assigned later

    constructor() {
        this.init();
    }
    init() { this.name = "Anna"; }
}

The ! after a field name (definite assignment) tells TypeScript that the property will be initialized, even if not in the constructor. It avoids the "not initialized" error. Use it carefully.

satisfies (TS 4.9+)
const config = {
    host: "localhost",
    port: 3000,
} satisfies Record<string, string | number>;

config.port.toFixed(); // still inferred

The satisfies operator validates that a value fulfills a type without losing the specific inference. Unlike : type, it keeps the exact types of the properties. It combines safety and precision.

@ts-expect-error
// @ts-expect-error: intentionally invalid value
const n: number = "text";

// @ts-ignore: suppresses the next error
const x: string = 123;

The @ts-expect-error comment suppresses an expected error and warns if it stops existing (better than @ts-ignore). Use them sparingly and with justification, never in bulk.