TypeScript Basics
Static typing fundamentals that catch bugs at compile time and improve IDE support across JavaScript projects.
Why TypeScript in Production
TypeScript adds a static type layer on top of JavaScript, catching category errors — wrong argument types, missing properties, null access — before code ships. Refactors become safer because the compiler validates every callsite when interfaces change.
Teams adopt TypeScript not for academic purity but for velocity at scale: autocomplete, inline documentation, and fail-fast CI pipelines reduce production incidents and onboarding time for large codebases.
- Catch errors at compile time, not in production
- IDE autocomplete and inline docs
- Safer refactors across large teams
Setup and tsconfig.json
Install TypeScript as a dev dependency and add a `tsconfig.json` at the project root. Key options include `target` (output JS version), `module` (module system), `strict` (enable all strict checks), and `include`/`exclude` for file globs.
Run `tsc --noEmit` in CI to typecheck without emitting files when a bundler handles compilation. Align editor TypeScript version with the project version to avoid inconsistent diagnostics.
- `strict: true` for new projects
- `tsc --noEmit` in CI pipelines
- Match editor TS version to project
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true,
"skipLibCheck": true
}
}Primitive and Object Types
Annotate variables with `: string`, `: number`, `: boolean`, `: bigint`, `: symbol`, and `: undefined` / `: null` as needed. Arrays use `string[]` or `Array<string>`. Tuples fix length and per-index types: `[string, number]`.
The `any` type disables checking — avoid it except during incremental migration. Use `unknown` when the type is genuinely unknown and narrow before use.
- Prefer `unknown` over `any`
- Tuples for fixed-length arrays
- Union with `null` for nullable values
let name: string = 'Alice'; let scores: number[] = [90, 85]; let pair: [string, number] = ['id', 1];
Type Inference
TypeScript infers types from initializers, return statements, and control flow. Explicit annotations are optional when inference is clear — over-annotating adds noise without safety gains.
Control flow analysis narrows types inside `if` blocks: checking `if (value !== null)` tells the compiler `value` is non-null in that branch. Leverage inference by writing idiomatic JavaScript and letting the compiler do the rest.
- Inference from initializers and returns
- Control flow narrows union types
- Annotate public APIs; infer locals
const ids = users.map(u => u.id); // inferred as number[]
if (user) { user.name; } // narrowed from User | nullFunctions and Signatures
Type parameters, return types, and optional/default parameters mirror JavaScript with added type safety. Use void for functions that return nothing meaningful. Never return `any` from public APIs.
Function overloads declare multiple call signatures for a single implementation — useful for APIs whose return type depends on input shape. Prefer union parameters and conditional return types in modern code when overloads become unwieldy.
- Type params, returns, and optional args
- Overloads for input-dependent returns
- Public APIs need explicit return types
function greet(name: string, excited = false): string {
return excited ? `Hi, ${name}!` : `Hi, ${name}`;
}