TypeScript Cheat Sheet: Types, Generics and Utility Types (With the any Trap)
β‘ Quick Answer
A practical TypeScript cheat sheet β basic types, interfaces vs types, generics, the five utility types you actually use, and why any is worse than unknown.
Get more content like this on Telegram!
Daily AI tips, notes & resources β free
Advertisement
TypeScript Cheat Sheet
TypeScript is JavaScript with a type checker attached. The syntax you look up is small; the part that takes time is knowing which construct to reach for.
This sheet covers the daily working set, the five utility types that solve most real problems, and the any trap that quietly disables the whole point of using TypeScript.
Updated for 2026. TypeScript 5.x.
Basic Types
let name: string = "Ada";
let age: number = 36;
let active: boolean = true;
let items: string[] = ["a", "b"];
let pair: [string, number] = ["a", 1]; // tuple
let anything: unknown = getExternal(); // safe catch-all
let nothing: null | undefined;
// union
type Status = "draft" | "published" | "archived";
// literal + inference
const role = "admin"; // type is "admin", not string
let role2 = "admin"; // type is stringLet TypeScript infer. Writing const name: string = "Ada" adds nothing β the compiler already knows. Annotate function parameters, return types on public functions, and anything the compiler cannot work out. Annotating everything is noise.
Objects: interface vs type
interface User {
id: number;
name: string;
email?: string; // optional
readonly createdAt: Date; // cannot be reassigned
}
type Point = { x: number; y: number };
// unions β only possible with `type`
type Result = { ok: true; data: string } | { ok: false; error: string };
// extending
interface Admin extends User { permissions: string[] }
type AdminT = User & { permissions: string[] };| Use | Choose |
|---|---|
| Object shape that may be extended, public API | interface |
| Union, intersection, tuple, function signature | type |
| Alias for a primitive or computed type | type |
The practical difference is small enough that consistency within a codebase matters more than the technical distinction.
Functions
function add(a: number, b: number): number {
return a + b;
}
const multiply = (a: number, b: number): number => a * b;
function greet(name: string, greeting = "Hello"): string { ... }
function log(msg: string, ...tags: string[]): void { ... }
// function type
type Handler = (event: string, data?: unknown) => void;
// never β this function does not return
function fail(msg: string): never {
throw new Error(msg);
}void versus never: void means the function returns nothing useful. never means it never returns at all β it always throws or loops forever. never is also what an exhaustive switch narrows to, which is how you get compile-time exhaustiveness checking:
function render(s: Status) {
switch (s) {
case "draft": return "Draft";
case "published": return "Live";
case "archived": return "Archived";
default:
const exhaustive: never = s; // error if a Status is unhandled
return exhaustive;
}
}Add a fourth value to Status and this fails to compile, pointing you at every place that needs updating. That is one of the genuinely high-value patterns in TypeScript.
The Utility Types Worth Memorising
These five cover most real-world typing problems.
interface User { id: number; name: string; email: string; password: string }
Partial<User> // every property optional β update payloads
Required<User> // every property required
Pick<User, "id" | "name"> // { id: number; name: string }
Omit<User, "password"> // everything except password
Record<string, number> // { [key: string]: number }Omit is the one you will reach for most, usually to strip something internal before returning data:
type PublicUser = Omit<User, "password">;
type UserUpdate = Partial<Omit<User, "id">>; // update anything but the idThree more that save real duplication:
ReturnType<typeof getUser> // the type getUser returns
Awaited<ReturnType<typeof f>> // ...with the Promise unwrapped
Parameters<typeof getUser> // its parameters as a tupleThese derive types from code that already exists, so they cannot drift out of sync the way a hand-written duplicate would.
Generics
A generic preserves the relationship between input and output.
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first([1, 2, 3]); // number | undefined
first(["a", "b"]); // string | undefinedWithout the generic you would return any and lose everything the compiler knew.
// constrained generic
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
// generic interface
interface ApiResponse<T> {
data: T;
error: string | null;
}
const res: ApiResponse<User[]> = await getUsers();Do not add generics preemptively. A generic used with only one type is complexity with no benefit. The signal you need one is writing the same function three times for three types.
Narrowing
TypeScript follows your runtime checks and narrows types accordingly.
function process(value: string | number) {
if (typeof value === "string") {
value.toUpperCase(); // narrowed to string here
} else {
value.toFixed(2); // narrowed to number here
}
}
if (value instanceof Date) { ... }
if ("email" in user) { ... }
if (value !== null) { ... }Discriminated unions are the cleanest pattern in the language:
type Result =
| { ok: true; data: string }
| { ok: false; error: string };
function handle(r: Result) {
if (r.ok) {
console.log(r.data); // TypeScript knows data exists
} else {
console.log(r.error); // and that error exists here
}
}The shared literal field (ok) lets the compiler work out which branch you are in. This models success-or-failure far better than throwing, and it is enforced at compile time.
any vs unknown β The Central Trap
const a: any = getData();
a.whatever.nonsense(); // compiles. crashes at runtime.
const u: unknown = getData();
u.whatever; // error β narrow it first
if (typeof u === "string") {
u.toUpperCase(); // fine
}any does not mean "any type". It means "stop checking".
It is also contagious. Anything derived from an any is itself any, so a single any at the edge of your data flow can silently disable checking through an entire module. Teams often discover this when they enable noImplicitAny and find hundreds of places where the compiler had quietly given up.
Use unknown for anything external β API responses, JSON.parse, user input, third-party data. It accepts everything on the way in and forces you to prove what it is before use.
Typing an API Response Properly
The tempting version, and why it is wrong:
// UNSAFE β a lie to the compiler
const user = await res.json() as User;res.json() returns any. The assertion tells TypeScript to trust you. If the API changes a field, you get a runtime crash with no compile-time warning β which is the exact failure TypeScript exists to prevent.
A type guard is the minimum:
function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null
&& "id" in x && "name" in x;
}
const data: unknown = await res.json();
if (!isUser(data)) throw new Error("Unexpected response shape");
// data is User from hereA schema library is better for anything non-trivial, because it keeps the runtime check and the compile-time type as a single source of truth:
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>; // type derived from the schema
const user = UserSchema.parse(await res.json());Hand-written guards and hand-written types drift apart. Derived ones cannot.
Configuration Worth Setting
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true
}
}strict: true is not optional. Its most important component, strictNullChecks, stops null and undefined being assignable everywhere β without it, TypeScript permits calling a method on something that might be null, which is the most common runtime error in JavaScript.
noUncheckedIndexedAccess is the underrated one. It makes arr[0] typed as T | undefined rather than T, which is honest β indexing into an array genuinely might not find anything. It produces some friction and prevents a real category of crash.
The Four Traps
1. as assertions used as a fix. An assertion silences the compiler without changing reality. If you are reaching for as to make an error go away, the type is probably wrong. Legitimate uses exist but are rare.
2. any on external data. Use unknown and narrow. any spreads silently.
3. Non-null assertion ! by reflex. user!.name tells the compiler the value is definitely present. If it is not, you get the exact crash TypeScript was meant to prevent.
4. Trusting res.json(). It is any. Validate it.
Print This Section
DEFAULT let TS infer Β· annotate params and public return types
SHAPES interface = objects/extension type = unions/tuples/functions
UTILITY Partial<T> Β· Pick<T,'a'> Β· Omit<T,'a'> Β· Record<K,V>
ReturnType<typeof fn> Β· Awaited<T>
EXTERNAL unknown, never any β narrow with a guard or Zod
res.json() is any β validate it
CONFIG "strict": true (non-negotiable)
TRAP `as` and `!` silence the compiler without changing realityTypeScript's value comes almost entirely from strict mode and from refusing any at your data boundaries. A codebase with neither is paying the syntax cost and collecting very little of the benefit.
π Next in this collection: React Developer Cheat Sheet, or return to The Complete Developer Cheat Sheet Collection.
Advertisement
π¬ DiscussionPowered by GitHub Discussions
Frequently Asked Questions

AI & Software Engineering Editorial Team
The AiTechWorlds editorial team writes and reviews in-depth guides on artificial intelligence, machine learning, prompt engineering, programming, and developer tools. Every article is fact-checked against primary sources and kept up to date for working developers and CS students.
Not sure yet? Ask AI about this article
Get an instant, unbiased AI summary of βTypeScript Cheat Sheet: Types, Generics and Utility Types (With the any Trap)β.
Advertisement
Related Articles
Bash Scripting Cheat Sheet (The First Line Every Script Needs)
A practical Bash scripting cheat sheet β variables, conditionals, loops, functions, and the set -euo pipefail line that turns silent failure into loud failure.
15 Coding Interview Patterns That Solve Most Problems
The 15 coding interview patterns that cover most problems β the signal that identifies each one, a minimal code skeleton, and a two-minute recognition table.
CSS and Tailwind Cheat Sheet: Flexbox, Grid and Centering Solved
A practical CSS and Tailwind cheat sheet β Flexbox vs Grid decision rules, every centering method, responsive breakpoints, and the specificity traps.
Data Structures and Big-O Cheat Sheet for Coding Interviews
A practical big O cheat sheet β the complexity ladder with real examples, data structure operation tables, and how to state your complexity in an interview.