Back to TypeScript tutorials
Basic14 min read

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 | null

Functions 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}`;
}

Get In Touch


Ready to discuss your next project? Drop me a message.