Cheat sheet · TypeScript From Zero to Confident
TypeScript From Zero cheat sheet
TypeScript from setup to strict migration: narrowing, objects and classes, generics, utility and mapped types, modules, runtime validation, and reading errors. Types are erased, so the checker never sees real data.
blogs · jp-blogs.vercel.app/cheatsheets/series/typescript
What TypeScript isLesson 0 →
| Tool | Checks types? | Use it for |
|---|---|---|
tsc | Yes (the only one) | tsc --noEmit in CI and editors |
tsx file.ts | No | Run now (strips types) |
node file.ts | No | Run on Node 22.18+ (strips types) |
- TypeScript = JavaScript + a checker.
interface,typeand: stringare deleted, so you cannot test them at runtime. - Node's stripping fails on enums, parameter properties and runtime namespaces. It ignores
tsconfig.json, sopathsdo not work.
Watch out: JSON.parse(s) as { id: number } passes tsc and still crashes at runtime. as is an instruction, not a conversion.
A starter tsconfigLesson 0 →
{ "compilerOptions": {
"target": "es2022", "module": "nodenext",
"strict": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"allowImportingTsExtensions": true,
"noEmit": true, "types": ["node"] },
"include": ["src"] }- strict
- Null checks,
noImplicitAnyand more (default since TS 6) - noUncheckedIndexedAccess
arr[i]becomesT | undefined; not instrict- skipLibCheck
- Skip
.d.tschecks: faster, hides library errors - verbatimModuleSyntax
- Imports kept as written; types need
import type
Basic types and inferenceLesson 1 →
- Primitives are lowercase:
string,number,boolean,bigint,symbol,null,undefined. Tuple:[string, number]. let x = "a"isstring(widened).const x = "a"is"a"(literal).{ dir: "up" }hasdir: string; fix with an annotation oras const.- Let inference work for locals. Write parameter types, exported return types, and empty starts.
| Type | Meaning | Calls allowed? |
|---|---|---|
any | Checker off, spreads everywhere | Anything |
unknown | Safe unknown: narrow first | None until narrowed |
never | No value (throws, impossible) | n/a |
Narrowing and unionsLesson 1 →
type Shape =
| { kind: "circle"; r: number }
| { kind: "square"; side: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle":
return Math.PI * s.r ** 2;
case "square": return s.side ** 2;
default: return fail(s); // takes never
}
}- Narrow with
typeof,instanceof,in,=== null, truthiness,Array.isArray, or akindtag. - Traps:
typeof nullis"object";if (!count)also drops0. x!adds nothing at runtime. Use a real check that throws,??, or?..
Objects, interface vs typeLesson 2 →
| Topic | interface | type |
|---|---|---|
| Declared twice | Merges | Error TS2300 |
| Unions, tuples, functions | No | Yes |
| Extend | extends (clash errors early) | & (clash becomes never) |
?makes a property optional (adds| undefined).readonlyis compile time only; useObject.freezefor real.- Index signature
{ [k: string]: number }reads asnumber | undefinedwithnoUncheckedIndexedAccess. Known keys:Record<"a" | "b", number>.
Structural typing and excess checksLesson 2 →
- Shape decides, not the name. Two classes with the same fields are interchangeable unless branded.
- Excess property check: only for an object literal written in place (
TS2561, with a "Did you mean" hint). Not for a variable or a cast. - Weak type (
TS2559): an all-optional type with no property in common is refused.
const dirs = ["up", "down"] as const;
type Dir = (typeof dirs)[number];
// "up" | "down"
const p = { red: [255, 0], green: "#0f0" }
satisfies Record<string, string|number[]>;
p.red.map(n => n); // precise type keptsatisfies checks and keeps precision; an annotation widens; as skips the check.
Classes and enumsLesson 2 →
| Feature | Behaviour |
|---|---|
private | Compile time only; visible in Object.keys |
#pin | Real runtime private; hidden from keys |
| Parameter property | constructor(public x): needs codegen, banned by erasableSyntaxOnly |
enum | Runtime object; prefer a union of literals |
Watch out: const { method } = obj; method() loses this and crashes with no tsc error. Use an arrow field, bind, or call obj.method().
Function types and overloadsLesson 3 →
- Optional and default parameters go last; rest
...xs: number[]collects the rest. void= ignore the result (() => 42fits() => void).never= never returns.- Overloads: several body-less signatures plus one implementation. Callers see only the overloads, tried in order (
TS2769if none fit). Prefer a union or generic. thisparameter:function who(this: { name: string })is erased and stops lost-thiscalls (TS2684).
GenericsLesson 3 →
function first<T>(xs: T[]): T | undefined {
return xs[0];
}
function len<T extends { length: number }>(
x: T) {
return x.length; // T kept
}
function get<T, K extends keyof T>(
o: T, k: K): T[K] {
return o[k]; // wrong key = error
}- Inference reads arguments and callbacks. With nothing to infer (empty list), pass the type argument yourself.
- A bare type parameter has no members (
TS2339); addextends. If it appears once in the signature, drop it. keyof= key union.typeof v= a value's type. Indexed accessT[K].(typeof a)[number]= item type.
Watch out: A generic fetch that only casts T is a lie: make the caller pass a parser. For an event emitter, one map from event name to payload, with K extends keyof E, ties every event to its data.
Utility typesLesson 4 →
| Type | Result |
|---|---|
Partial / Required | All optional / all required |
Pick / Omit | Keep / drop object keys |
Record | Object with given keys and value type |
Exclude / Extract | Drop / keep union members |
ReturnType, Parameters | Read a function type |
Awaited | Type after await |
- PATCH body:
PartialofOmitof the User withoutid. Async result:AwaitedofReturnTypeof the function. Omiton a union collapses to the shared keys. Make a distributive helper.
Mapped, conditional, template typesLesson 4 →
type P2<T> = { [K in keyof T]?: T[K] };
type Mut<T> =
{ -readonly [K in keyof T]-?: T[K] };
type Getters<T> = {
[K in keyof T
as `get${Capitalize<string & K>}`]:
() => T[K];
};
type Ret<T> =
T extends (...a: any[]) => infer R
? R : never;as renames keys (never drops them). [T] wrapping stops distribution.
- Looping over
keyof Tkeeps modifiers.+/-add or removereadonlyand?. - A naked type parameter in
extendsdistributes over a union. Wrap both sides in[ ]to stop it; needed to test fornever. - Template types build string unions, such as
"onClick" | "onFocus". Stop when the type is harder than the code (TS2589= too deep).
Modules and declaration filesLesson 5 →
| Project | module | moduleResolution |
|---|---|---|
| Node app or library | nodenext | nodenext |
| Vite or Next.js | esnext / preserve | bundler |
- Under
nodenextwrite the output extension:import "./util.js"in a.tsfile. pathshelps only the checker.nodestill fails withERR_MODULE_NOT_FOUND; add a bundler alias orimportsinpackage.json.@types/xgives types to JS libraries (TS7016if missing). A.d.tswith a top-levelimport/exportaugments instead of declaring.__dirnamepassestscbut crashes in ESM. Useimport.meta.dirname.- Monorepo:
referencesandtsc -b. Library:declaration,exportswithtypesfirst, check with@arethetypeswrong/cli.
// env.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string }
}
}
export {};React, Express and runtime validationLesson 6 →
| Case | Write |
|---|---|
| Empty state | Give useState a type argument: User | null, string[] |
| Events | ChangeEvent of the element, or an inline handler |
| Element ref | useRef starts null: use ref.current?.focus() |
| Reducer | Action union with a type tag |
| Express | Request params, body, query; req.body is any |
Watch out: A hand-written guard x is Todo is trusted fully; checking only id lets a bad done through. Brand same-shaped ids (string & { __brand: "UserId" }) in one validated place.
const User = z.object({ id: z.number() });
type User = z.infer<typeof User>;
const r = User.safeParse(req.body);
if (!r.success)
return res.status(400).json(r.error);
const env = Env.parse(process.env);parse throws; safeParse returns { success, data or error }. Parse process.env once at startup; use z.coerce.number().
Common errors and fixesLesson 7 →
| Code | Means | Fix |
|---|---|---|
TS2322 | Type A not assignable to B | Fix the value or widen the type |
TS2339 | Property does not exist | Fix the name or the type |
TS2345 | Wrong argument type | Pass the right type |
TS18048 / TS2532 | Possibly undefined | if, ?., ?? |
TS2307 | Cannot find module | Path, extension, install, declare module |
- Read errors from the bottom: the last indented line is the cause.
tscshows the first difference only. - Use
@ts-expect-errorwith a reason, not@ts-ignore: a stale one fails withTS2578.
Migrating to strictLesson 7 →
- Add
allowJsand atsconfig. Keep the build green. - Add
checkJs(or// @ts-checkper file) with JSDoc types. - Rename to
.tsa folder at a time, still non-strict. - Turn on
noImplicitAnyfirst (anyhides other errors), thenstrictNullChecks. - Then the other
strictflags one by one;noUncheckedIndexedAccesslast. - Commit after each step: all at once gave 18 errors, in steps 10, then 1 at a time.
- CI:
tsc --noEmit(ortsc -b). Plaintscexits 2 but still writes JS. typescript-eslintaddsno-floating-promises, no explicitany, no!, no@ts-ignore.
Interview one-linersLesson 0 →
| Question | Short answer |
|---|---|
| any vs unknown? | any switches checks off; unknown forces a narrow first. |
| interface vs type? | interface merges; type names unions, tuples, functions. |
| Enum or union? | Union of literals: no runtime code. as const object if you need the list. |
| Why can types not protect API data? | They are erased; validate at the edge with a schema. |
When is as fine? | A check the compiler cannot follow, inside a tested helper. Never on outside data. |
A cheat sheet is a reminder, not a teacher. Each card links to the lesson that explains it step by step.