typescript · memo
In one line: Types are erased: the compiler proves consistency inside the program,
so the senior’s job is the edges — a strict tsconfig, module settings that
match whoever actually loads the code, runtime validation where data enters,
and a build that stays fast as the codebase grows.
Download PDF Print view LaTeX source
How it works — config
strict=noImplicitAny,noImplicitThis,alwaysStrict,strictNullChecks,strictFunctionTypes,strictBindCallApply,strictPropertyInitialization,useUnknownInCatchVariables(4.4),strictBuiltinIteratorReturn(5.6). New members join on upgrade — pin TS versions.- Not in strict, worth it:
noUncheckedIndexedAccess(a[i]:T|undefined),exactOptionalPropertyTypes,noImplicitOverride,noImplicitReturns,noFallthroughCasesInSwitch. - Modules:
nodenextmodels Node exactly —.mts/.ctsand"type": "module"decide ESM vs CJS per file; relative imports name the output (./util.js; 5.7rewriteRelativeImportExtensionsallows.ts).bundler(5.0) +module: esnext/preserve(5.4): no extensions. Interop:esModuleInteropemits helpers soimport x from 'cjs'getsmodule.exports. verbatimModuleSyntax(5.0): what you write is what is emitted —import type {A}vanishes,import {type A}leavesimport {} from 'a'(side effect kept).isolatedModules: forbids what a per-file transpiler can’t do (type re-exports w/oexport type, ambientconst enum).- Declarations:
.d.ts= types only. Untyped lib:declare module 'x'(bare = allany) or@types/*(DefinitelyTyped);"types"limits auto-included globals;skipLibCheck: faster, hides clashes. - Project references:
composite+references;tsc -brebuilds only stale projects (.tsbuildinfo) and enforces layering (Swift: SPM targets). - Speed:
-0pt-extendedDiagnostics,-0pt-generateTrace; interfaces over big&; annotate exported returns (isolatedDeclarations5.5: parallel.d.ts); tame mega-unions. TS 7 = native Go port (previewed 2025).
Example — the edges
const User = z.object({ id: z.string(), age: z.number() });
type User = z.infer<typeof User>; // one source of truth
async function loadUser(res: Response): Promise<User> {
return User.parse(await res.json()); // throws on bad shape
}
declare const tag: unique symbol; // brand: nominal, 0 bytes
type UserId = string & { readonly [tag]: 'UserId' };
const toUserId = (s: string) => s as UserId; // the one cast
const Dir = { Up: 'up', Down: 'down' } as const;
type Dir = (typeof Dir)[keyof typeof Dir]; // 'up' | 'down'
// globals.d.ts -- has export, so it is a module: augment
export {};
declare global { var __APP_VERSION__: string }
// assets.d.ts -- no import/export: a script, ambient modules
declare module '*.png' { const id: number; export default id; }
Enums vs unions
enumemits a runtime object; numeric ones add a reverse map (Object.valuesgives names and numbers); a string enum rejects the bare literal'up'— nominal-ish.const enuminlines, but breaks per-file transpilers and package boundaries. 5.8erasableSyntaxOnlybans enums, namespaces, parameter properties (Node type stripping). Default: literal union /as const.
Interview traps
JSON.parseandResponse.json()returnany: the lie spreads silently. Type the resultunknown, then parse.catch (e)isunknownunder strict:e instanceof Errorbeforee.message.pathsaliases are not rewritten in output — Metro/Jest/Node need the same alias (babel-plugin-module-resolver,moduleNameMapper).declare globalin a file with noimport/exportis an error: addexport {}.@ts-ignorehides every future error on that line;@ts-expect-error(3.9) fails once the error is gone — self-cleaning.
JS → TS migration
allowJs (mixed build) → checkJs/// @ts-check + JSDoc →
rename leaves first (utils, API client) upward; type the boundaries first — most value.
Turn strict on early with a @ts-expect-error baseline (or a stricter tsconfig
for converted dirs); lint no-explicit-any, track the count down.
Remember
Strict inside · parse at the edge · config matches the loader.
Likely questions
node16/nodenextvsbundler? — who resolves imports at runtime.- Why
verbatimModuleSyntax? — per-file transpilers can’t know what is a type. - Nominal IDs? — a brand:
string & {[tag]: 'UserId'}. - Why validate if we have types? — they’re erased; the server/storage can lie.
- Slow
tsc? — trace it, split with project references, annotate exported returns, shrink mega-unions.