Quick take: This TypeScript cheat sheet is a scannable reference for the syntax you reach for every day: basic types, arrays, interfaces, functions, generics, utility types, and the tsconfig flags that matter. It stays version-neutral because the syntax barely moves across TypeScript 5.x, 6, and 7. Current stable is 7.0, which shipped July 8, 2026, but nothing below changes between versions.
Bookmark this and skip the handbook deep dives. A good TypeScript cheat sheet answers one question fast: what's the exact syntax for the thing I half-remember? Below you'll find every core annotation, one section at a time, with a short plain-language note and a compact code block. I use this same reference myself when I switch back to TypeScript after a week in a Python repo and my fingers forget where the colons go.
What are the basic types?
Every value in TypeScript carries a type, whether you write it out or let inference figure it out. These are the primitives plus the two escape hatches you'll meet constantly. The pair that trips people up is any versus unknown, so read the note under the block.
let title: string = "cheat sheet";
let count: number = 42;
let ready: boolean = true;
let nothing: null = null;
let missing: undefined = undefined;
// escape hatches
let loose: any = fromAnywhere; // opts out of checking, avoid it
let safe: unknown = fromApi; // must be narrowed before use
// bottom type: a value that can never occur
function fail(msg: string): never {
throw new Error(msg);
}
Here's my rule, and I'll die on this hill: any is a bug you haven't found yet. It silently switches off the compiler, so errors sail straight through to production. Reach for unknown instead and let TypeScript force a check before you touch the value. Do you really trust that a JSON response is shaped the way you assumed at 2am?
How do arrays and tuples work?
Arrays hold many values of one type. Tuples are fixed-length arrays where each slot has its own type and the order is part of the contract, think coordinate pairs, or the [value, setValue] you get back from a React useState call.
let tags: string[] = ["ts", "types"];
let scores: Array<number> = [98, 72, 88]; // same thing, generic form
let grid: number[][] = [[1, 2], [3, 4]];
// tuple: fixed length, one type per position
let point: [number, number] = [12, 40];
let flagged: [string, boolean] = ["ok", true];
// labeled tuple with a rest element
let user: [id: number, ...roles: string[]] = [1, "admin", "editor"];
The string[] shorthand and the Array<string> generic form compile to the exact same thing, so use whichever reads better to you. I stick with the bracket form for primitives and switch to the generic form only when the element type is itself long or nested.
Interface vs type: which should you use?
Both describe the shape of an object, and for a plain object they're interchangeable. interface supports declaration merging and reads cleanly when other code extends it. type is a Swiss Army knife: it also names unions, tuples, and function signatures, and it powers mapped and conditional types.
interface User {
id: number;
name: string;
admin?: boolean; // optional property
readonly createdAt: Date;
}
interface Admin extends User {
permissions: string[];
}
// type does object shapes too, plus much more
type Point = { x: number; y: number };
type ID = string | number; // union, interface can't do this
type Handler = (event: string) => void; // function signature
So which do I default to? For public library types and anything that might get extended, interface. For unions, function types, and computed types, type. Honestly, on an app team the difference rarely bites you, so the real rule is pick one convention per file and don't mix styles halfway down.
How do you type functions?
Functions want types on their parameters and, ideally, an explicit return type so a stray refactor can't quietly change what comes out. TypeScript handles optional params, default values, and even overloads for the awkward cases.
// typed params + explicit return type
function add(a: number, b: number): number {
return a + b;
}
// optional and default parameters
function greet(name: string, loud = false, title?: string): string {
const who = title ? `${title} ${name}` : name;
return loud ? `HI ${who}` : `hi ${who}`;
}
// overloads: same function, different call shapes
function parse(input: string): object;
function parse(input: number): string;
function parse(input: string | number): object | string {
return typeof input === "string" ? JSON.parse(input) : String(input);
}
I let TypeScript infer return types for tiny one-liners, but on anything exported I write the return type by hand. It's a cheap guardrail, and it turns a subtle logic change into a loud red error at the function itself instead of three files away.
How do unions, literals, and narrowing fit together?
A union says a value is one of several types. Literal types pin a value to exact strings or numbers, which makes them perfect for named options. Narrowing is how TypeScript figures out which branch you're in so it can hand you the right autocomplete.
type Status = "idle" | "loading" | "done" | "error"; // literal union
function render(s: Status): string {
if (s === "loading") return "Spinner";
if (s === "error") return "Retry";
return "Content";
}
// narrowing a union by checking typeof
function pad(value: string | number): string {
if (typeof value === "number") {
return value.toFixed(2); // value is number here
}
return value.trim(); // value is string here
}
Literal unions like Status are the workhorse of well-typed apps. They give you autocomplete on every valid state and a compile error the moment you typo "loadng". Why reach for anything heavier when four strings say it all?
What does generic syntax look like?
Generics let a function or type work over many types without losing what it knows. The angle brackets hold a type variable, usually T, that gets filled in at the call site. Constraints with extends keep that variable honest.
// basic: the return type mirrors the argument type
function first<T>(list: T[]): T | undefined {
return list[0];
}
const n = first([1, 2, 3]); // n is number | undefined
const s = first(["a", "b"]); // s is string | undefined
// constrained: T must have a length property
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
Generics are where the language gets genuinely powerful, and also where it gets confusing fast. If the <T> soup starts hurting your head, I wrote a longer walkthrough in TypeScript generics explained that builds them up from scratch with worked examples.
Which utility types should you memorize?
TypeScript ships built-in helpers that transform existing types so you don't rewrite shapes by hand. These five cover the vast majority of day-to-day work. Keep this table handy; I still glance at it weekly.
| Utility | What it does |
|---|---|
Partial<T> | Makes every property of T optional |
Pick<T, K> | Keeps only the named keys K from T |
Omit<T, K> | Drops the named keys K from T |
Record<K, V> | Builds an object type with keys K and values V |
ReturnType<F> | Extracts the return type of function F |
interface User { id: number; name: string; email: string }
type Draft = Partial<User>; // all optional
type Card = Pick<User, "id" | "name">; // just id + name
type Safe = Omit<User, "email">; // everything but email
type ById = Record<number, User>; // { [id: number]: User }
type R = ReturnType<typeof first>; // T | undefined
There are a dozen more, Required, Readonly, NonNullable, Awaited, and each earns its keep in the right spot. For the full set with real refactoring examples, see the TypeScript utility types guide.
Enums or a union of literals?
Enums name a set of related constants and emit a real object at runtime. A union of string literals does much the same job with zero runtime cost, which is why a lot of teams, mine included, now default to the union.
// enum: generates runtime code
enum Direction { Up, Down, Left, Right }
let move: Direction = Direction.Up; // 0
// union of literals: no runtime output at all
type Dir = "up" | "down" | "left" | "right";
let step: Dir = "up";
My take: reach for the literal union first. It's lighter, it plays nicely with JSON, and it never surprises anyone with a mystery numeric value in the bundle. Keep enums for the rare case where you actually need reverse lookups or a guaranteed integer.
Which tsconfig options actually matter?
You can spend an afternoon reading every compiler flag, or you can set three and move on. Turn on strict, choose a modern module resolution, and target a recent JavaScript level. That covers most projects.
{
"compilerOptions": {
"strict": true,
"moduleResolution": "bundler",
"target": "es2022",
"module": "esnext",
"skipLibCheck": true
}
}
strict is the big one; it flips on the whole safety family, strictNullChecks, noImplicitAny, and friends, in a single line. moduleResolution: "bundler" matches how Vite and esbuild actually resolve imports, and target: "es2022" stops the compiler from down-leveling perfectly good modern syntax. If you want the flag-by-flag breakdown of what strict really switches on, read the TypeScript strict mode guide. None of this changed in TypeScript 7, by the way; the syntax on this whole cheat sheet is stable across 5.x, 6, and 7.
Related
- TypeScript 7 and Project Corsa overview - what the current stable version changed under the hood