TypeScript for JavaScript Engineers
A practical on-ramp: what TS adds to JS, strict mode basics, gradual migration, and the frontend patterns that matter first.
- typescript
- typescript-for
If you already write modern JavaScript, TypeScript is not a new language so much as JavaScript plus a type layer that erases at compile time. The runtime is still JS. The win is earlier feedback: wrong props, bad API shapes, and null access fail in the editor instead of in production.
Docs: TS for JS Programmers, Handbook.
What changes day one
// JS
function greet(name) {
return 'Hello ' + name.toUpperCase();
}
// TS
function greet(name: string) {
return 'Hello ' + name.toUpperCase();
}
greet(42); // compile error
Syntax you already know stays: async/await, modules, classes, destructuring. You add annotations, interfaces/types, and generics as needed.
Mental model
- Types describe values — they don’t exist at runtime.
- Inference is default — annotate boundaries (exports, params) more than every local.
unknown>anyat trust boundaries.- Strict mode is the point; loose TS teaches bad habits.
const n = 1 + 2; // inferred number
const el = document.getElementById('app'); // HTMLElement | null
Tooling picture
.ts / .tsx → typecheck (tsc) → emit or bundler transform → JS in browser
With Vite/Next, the bundler strips types; tsc --noEmit (or vue-tsc, etc.) typechecks in CI. Both matter.
Gradual migration
allowJs+ rename files file-by-file to.ts/.tsx.- Enable
stricton new code; migrate old modules with// @ts-expect-errortickets. - Type public APIs first (shared utils, API clients, design-system props).
- Add schema validation for JSON — types alone don’t protect
fetch.
// Start
export function formatPrice(cents: number): string {
return `$${(cents / 100).toFixed(2)}`;
}
Frontend-first topics (priority order)
| Topic | Why |
|---|---|
| Primitives + object types | Props and state |
| Unions + narrowing | UI state machines |
unknown vs any |
API/JSON safety |
| Generics basics | Lists, hooks, utils |
| Utility types | Forms and DTOs |
| React prop/event types | Daily UI work |
Deep links: basic types, unknown vs any, narrowing, typing React props.
Common JS → TS confusions
Optional chaining already exists in JS — TS adds checking that you handled null:
user?.address?.city;
Default params work the same; types should match.
Destructuring needs types on the whole object:
function Button({ label, onClick }: { label: string; onClick: () => void }) {
return (
<button type="button" onClick={onClick}>
{label}
</button>
);
}
JSON.parse returns any historically — type as unknown and validate.
Don’t boil the ocean
Skip early: complex conditional types, heavy declaration merging, inventing a mini type language for one component. Ship typed props and strict null checks first.
Interview out-loud answer
“TypeScript is typed JavaScript that erases at compile time. I annotate boundaries, lean on inference, use strict null checks, prefer unknown at I/O, and migrate incrementally with CI typecheck. Types don’t replace runtime validation for untrusted data.”
Learning path (two weeks)
Days 1–3: primitives, unions, narrowing, strict null. Days 4–6: props, events, generics basics. Days 7–10: utility types, unknown at boundaries, small schema validation. Avoid advanced conditional types until those are comfortable in real PRs.
Related on this site
- Basic types and annotations
- tsconfig strict flags
- unknown vs any
- Interfaces vs type aliases
- Typing React props
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.