JavaScriptTypeScript: A Quick Tour

TypeScript Introduction

If you write JavaScript professionally, you will meet TypeScript — Microsoft's typed superset of JavaScript that has become the default for serious projects. The pitch is simple: keep everything you know about JavaScript, add type annotations, and catch a huge class of bugs before the code ever runs.

This page introduces TypeScript from a JavaScript developer's perspective: what it adds, how to adopt it gradually, and when the investment pays off. For a deep dive, this site has a full TypeScript tutorial series — this page is your on-ramp.

What TypeScript adds

TypeScript is JavaScript — every valid JS file is valid TS. On top of it, TypeScript adds a static type system that analyzes your code without running it. Consider this classic JavaScript bug:

JS
// JavaScript — runs, then explodes at runtime
function getInitials(user) {
  return user.firstName[0] + user.lastName[0]
}

getInitials({ firstName: 'Ada' })
// TypeError: Cannot read properties of undefined (reading '0')

The same code in TypeScript refuses to compile:

TS
type User = {
  firstName: string
  lastName: string
}

function getInitials(user: User): string {
  return user.firstName[0] + user.lastName[0]
}

getInitials({ firstName: 'Ada' })
// Error: Property 'lastName' is missing in type '{ firstName: string; }'
//        but required in type 'User'.
  • Errors at compile time, not runtime — typos, missing properties, wrong argument types are caught as you type

  • Self-documenting code — the signature tells you exactly what a function accepts and returns

  • Fearless refactoring — rename a property and the compiler lists every place that must change

  • Superhuman editor support — autocomplete, inline docs, and go-to-definition all come from types

Types disappear at runtime
TypeScript compiles to plain JavaScript by erasing the annotations. There is zero runtime cost and no TS code ships to the browser — types exist only for you, your editor, and the compiler.
Type annotations: the basics

TS
// Primitives
let title: string = 'Hello'
let count: number = 42
let active: boolean = true

// Arrays and objects
let tags: string[] = ['js', 'ts']
let point: { x: number; y: number } = { x: 1, y: 2 }

// Functions — parameter and return types
function repeat(text: string, times: number): string {
  return text.repeat(times)
}

// Union types — one of several
let id: string | number = 'usr_1'
id = 7 // also fine

// Optional properties and parameters
function greet(name: string, title?: string) {
  return title ? `${title} ${name}` : name
}

You annotate far less than you might fear, because TypeScript infers types from values:

TS
let score = 10          // inferred: number
score = 'high'          // Error: Type 'string' is not assignable to type 'number'

const langs = ['js', 'ts']       // inferred: string[]
const upper = langs.map((l) => l.toUpperCase()) // l inferred as string
Let inference work
Good TypeScript style annotates function boundaries (parameters and return types) and lets the compiler infer local variables. Annotating everything makes code noisy without adding safety.
Interfaces and type aliases

Shapes of objects are described with interface or type. They're nearly interchangeable for object shapes; interfaces can be extended and merged, type aliases can also name unions and other combinations:

TS
interface User {
  id: number
  name: string
  email?: string          // optional
  readonly createdAt: Date // cannot be reassigned
}

interface Admin extends User {
  permissions: string[]
}

// Type alias — same idea, plus unions
type Status = 'draft' | 'published' | 'archived'

function publish(user: Admin, status: Status) {
  if (!user.permissions.includes('publish')) {
    throw new Error(`${user.name} cannot publish`)
  }
  // ...
}

That Status union is a taste of what makes TypeScript special: the compiler knows the only legal values are those three strings, autocompletes them, and rejects 'pubished' (typo) instantly.

Gradual adoption: you don't have to rewrite anything

TypeScript was designed for incremental migration. A realistic path for an existing JavaScript project:

  1. Add a tsconfig.json with allowJs: true — TypeScript tooling now understands your .js files without changing them.

  2. Turn on checkJs: true — the compiler type-checks plain JavaScript using inference, flagging real bugs with zero annotations.

  3. Add JSDoc types where inference needs help — full type safety while the files stay .js.

  4. Rename files to .ts one at a time — start with leaf modules (utilities) and work inward.

  5. Ratchet up strictness — enable strict: true (or flag by flag: noImplicitAny, strictNullChecks) as the codebase gets cleaner.

Step 3 deserves a demo — JSDoc comments give you TypeScript checking in plain .js files:

JS
// utils.js — still plain JavaScript!
// @ts-check

/**
 * @param {number} price
 * @param {number} [taxRate]
 * @returns {number}
 */
export function withTax(price, taxRate = 0.2) {
  return price * (1 + taxRate)
}

withTax('9.99') // Error in your editor: Argument of type 'string'
                // is not assignable to parameter of type 'number'.
tsconfig.json basics

The tsconfig.json file controls the compiler. A solid starter for a modern project:

JSON
{
  "compilerOptions": {
    "target": "ES2022",          // JS version to emit
    "module": "ESNext",          // module system in output
    "moduleResolution": "bundler",
    "strict": true,              // all strict checks — always on for new code
    "noEmit": true,              // just type-check; a bundler emits the JS
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

Option

What it does

strict

Enables the full strict family — the single most valuable flag

target

How far down to transpile syntax (ES2022, ES2020, ...)

noEmit

Type-check only; let Vite/esbuild/SWC produce the JavaScript

allowJs / checkJs

Include and type-check plain .js files (gradual migration)

noImplicitAny

Error when a type silently falls back to any

strictNullChecks

Makes null/undefined explicit in types — kills a whole bug class

Avoid the any escape hatch
Typing a value as any turns off checking for everything it touches — errors silently spread through your code. Prefer unknown when you genuinely do not know a type; it forces you to check before use.
Compiling: tsc and Vite

Two common ways to run TypeScript:

Bash
# 1. The TypeScript compiler directly
npm install --save-dev typescript
npx tsc              # compile per tsconfig.json
npx tsc --noEmit     # type-check only
npx tsc --watch      # re-check on every save

# 2. Vite (most apps) — esbuild strips types instantly,
#    tsc checks types separately
npm create vite@latest my-app -- --template vanilla-ts

Modern setups split the jobs: a fast tool (esbuild/SWC) strips types during dev and build, while tsc --noEmit runs in CI to actually check them. That's why a Vite project's build script is typically tsc --noEmit && vite build.

Common first errors (and what they mean)

TS
const el = document.getElementById('app')
el.innerHTML = 'Hi' // Error: 'el' is possibly 'null'.
// Fix: check it first
if (el) el.innerHTML = 'Hi'

let user: { name: string }
console.log(user.name) // Error: Variable 'user' is used before being assigned.

function pick(key) {} // Error: Parameter 'key' implicitly has an 'any' type.
// Fix: function pick(key: string) {}

const config = JSON.parse(raw) // config is 'any' — annotate it:
const parsed: { port: number } = JSON.parse(raw)
$ npx tsc --noEmit
src/main.ts:2:1 - error TS2531: Object is possibly 'null'.
src/main.ts:8:9 - error TS7006: Parameter 'key' implicitly has an 'any' type.

Found 2 errors in the same file, starting at: src/main.ts:2

These errors feel pedantic at first — then you realize each one is a real runtime crash TypeScript just prevented. "Object is possibly null" is the compile-time version of JavaScript's most common production error.

When does TypeScript pay off?

Situation

Verdict

Team of 2+ working on shared code

Strong yes — types are enforced communication

Codebase you will maintain for years

Strong yes — refactoring safety compounds

Library published for others

Yes — consumers get autocomplete and type docs

Small script or throwaway prototype

Optional — JSDoc + // @ts-check may be enough

Learning JavaScript fundamentals

Learn JS first; types are easier once the base is solid

TypeScript vs JSDoc: JSDoc annotations with checkJs give you perhaps 80% of the safety with no build step — a great fit for small Node scripts and config files. Full TypeScript wins when you need advanced types (generics, unions, mapped types), a team-wide contract, or you're in an ecosystem (React, Angular, NestJS) where TS is the norm.

Where to go next

This page only scratches the surface — generics, narrowing, utility types like Partial and Pick, declaration files, and strict-mode patterns all await. This site has a dedicated TypeScript tutorial series that covers the language from setup to advanced types; head there when you're ready to go deep.

The one-line summary
TypeScript is JavaScript plus a compile-time safety net. You already know most of it — the types you add are documentation the compiler enforces for free.