TypeScript Complete Beginner Tutorial 2026

TypeScript is JavaScript with types. In 2026 it is the default choice for almost every serious frontend and backend project. This tutorial walks you from zero to a productive TypeScript developer: how to install it, the core type system, interfaces, generics, utility types, and how to add TypeScript to an existing JavaScript project without a rewrite.

Quick 2026 verdict

If you learn one thing beyond plain JavaScript in 2026, learn TypeScript. Every major framework (Next.js, Nuxt, SvelteKit, Angular, NestJS) is TypeScript-first. Every serious job posting assumes it. Learning curve is friendlier than people say: you can add types incrementally, and modern editors give you type hints automatically without typing anything.

What TypeScript actually is

TypeScript is a language that compiles to JavaScript. You write code with type annotations, the compiler checks them, then produces plain JavaScript that runs anywhere JavaScript runs (browsers, Node.js, Bun, Deno). The types disappear at runtime; they exist only during development to catch bugs.

// TypeScript (compiled)
function greet(name: string): string {
  return `Hello, ${name}`;
}
greet(123);  // Compile error: number is not string

// Emitted JavaScript (runtime)
function greet(name) {
  return `Hello, ${name}`;
}

Install TypeScript

mkdir my-ts-project
cd my-ts-project
npm init -y
npm install -D typescript @types/node
npx tsc --init

# Now write index.ts, then compile:
npx tsc index.ts
node index.js

# Or use tsx for one-step run (recommended in 2026):
npm install -D tsx
npx tsx index.ts

tsx (or Bun) executes TypeScript directly without a separate compile step. This is the modern default for scripts and small projects. For production builds you still compile with tsc or bundle with Vite/esbuild.

Basic types

// Primitives
let age: number = 25;
let name: string = 'Alice';
let isActive: boolean = true;
let nothing: null = null;
let notYet: undefined = undefined;

// Arrays (two equivalent syntaxes)
let scores: number[] = [90, 85, 78];
let names: Array<string> = ['Alice', 'Bob'];

// Tuples (fixed-length, fixed-type array)
let point: [number, number] = [10, 20];

// Objects
let user: { name: string; age: number } = { name: 'Alice', age: 25 };

// Any (escape hatch, use sparingly)
let anything: any = 42;
anything = 'string';    // no error, but no type safety

// Unknown (safer than any)
let value: unknown = getData();
if (typeof value === 'string') {
  value.toUpperCase();  // OK, narrowed to string
}

Let TypeScript infer

You do not have to annotate everything. TypeScript infers types automatically from initial values. Annotate function parameters and public API signatures; let the compiler figure out the rest.

// Over-typed (annoying, redundant)
let count: number = 0;
const items: string[] = ['a', 'b'];

// Idiomatic (TypeScript infers exactly the same)
let count = 0;
const items = ['a', 'b'];

// Where annotations DO help:
function calculate(price: number, tax: number): number {
  return price * (1 + tax);
}

interface and type

// interface: recommended for object shapes
interface User {
  id: string;
  name: string;
  email: string;
  age?: number;              // optional
  readonly createdAt: Date;  // read-only
}

const alice: User = {
  id: 'u1',
  name: 'Alice',
  email: '[email protected]',
  createdAt: new Date(),
};

// type alias: use for unions, primitives, computed types
type ID = string | number;
type Status = 'pending' | 'active' | 'archived';
type UserOrNull = User | null;

// Practical rule (2026): use interface for object shapes,
// use type for unions and utility-type compositions.

Union and literal types

// Union: value can be one of several types
function format(value: string | number): string {
  if (typeof value === 'number') {
    return value.toFixed(2);   // narrowed to number here
  }
  return value.trim();         // narrowed to string here
}

// Literal union: value must be one of these exact strings
type Direction = 'up' | 'down' | 'left' | 'right';
function move(dir: Direction) { /* ... */ }
move('up');       // OK
move('north');    // Error

// Discriminated unions (very common pattern)
type Success = { status: 'success'; data: User };
type Failure = { status: 'error'; message: string };
type Result = Success | Failure;

function handle(r: Result) {
  if (r.status === 'success') {
    console.log(r.data.name);       // narrowed to Success
  } else {
    console.error(r.message);       // narrowed to Failure
  }
}

Generics

Generics let you write reusable code that preserves type information across inputs and outputs.

// Without generics: you lose the type
function first(arr: any[]): any {
  return arr[0];
}
const x = first([1, 2, 3]);   // x is any, bad

// With generics: type flows through
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}
const y = first([1, 2, 3]);      // y is number
const z = first(['a', 'b']);     // z is string

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
}

async function getUser(id: string): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

Utility types you will actually use

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// Partial: all fields optional (great for update payloads)
type UserUpdate = Partial<User>;
// { id?: string; name?: string; email?: string; age?: number }

// Required: opposite of Partial
type RequiredUser = Required<UserUpdate>;

// Pick: choose specific fields
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: string; name: string }

// Omit: exclude specific fields
type UserWithoutEmail = Omit<User, 'email'>;

// Record: quick key-value type
type UsersById = Record<string, User>;
// { [key: string]: User }

// Readonly: prevent mutation
type FrozenUser = Readonly<User>;

// Awaited: unwrap a Promise
type UserFetched = Awaited<Promise<User>>;    // User

// ReturnType: extract a function's return type
function getUser() { return { id: '1', name: 'Alice' }; }
type UserFromFn = ReturnType<typeof getUser>;

tsconfig.json settings that matter

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,               // enables all strict checks
    "noUncheckedIndexedAccess": true,  // arr[i] returns T | undefined
    "noImplicitOverride": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

The single most important setting is "strict": true. It enables strict null checks, no-implicit-any, and other checks that catch real bugs. Never turn strict mode off just to silence errors; fix the underlying issue.

Add TypeScript to an existing JavaScript project

  1. Install: npm install -D typescript @types/node
  2. Add a tsconfig.json with "allowJs": true and "checkJs": false.
  3. Rename one file at a time from .js to .ts. Fix errors as they surface.
  4. Install @types/* packages for your dependencies: @types/express, @types/react, etc.
  5. Once fully converted, flip "strict": true and fix the resulting errors.

This staged approach lets you migrate a large codebase over weeks instead of blocking a release on a big-bang rewrite.

Common beginner mistakes

  • Sprinkling any everywhere. Kills the value of TypeScript. Use unknown if you truly do not know the type, then narrow.
  • Over-annotating. Let inference do its job. Annotate function parameters, return types on public APIs, and complex object shapes. Everything else is usually noise.
  • Trying to type everything perfectly on day one. Types are a tool, not a religion. Start loose, tighten as the shape stabilizes.
  • Not installing type packages. If a library has no built-in types, check DefinitelyTyped: npm install -D @types/library-name.
  • Treating errors as red squigglies to suppress. A TypeScript error usually indicates a real logic bug. Investigate before adding // @ts-ignore.

Frequently Asked Questions

How long does it take to learn TypeScript?

A few days to be productive if you already know JavaScript. A few weeks to feel fluent with generics, utility types, and advanced patterns. The learning is spread across many projects; you deepen your grasp as real-world use cases push you.

Do I need to know JavaScript first?

Yes. TypeScript is JavaScript plus types. Learn variables, functions, arrays, objects, async/await, and modules first, then add the type layer on top. Trying to learn both simultaneously usually causes confusion.

interface or type, which should I use?

Use interface for object shapes (better error messages, supports extension). Use type for unions, primitives, and computed types where interface cannot. Both are fine; do not spend hours agonizing over the choice.

Is TypeScript slower than JavaScript at runtime?

No. TypeScript compiles to JavaScript and the types are erased. At runtime the code is plain JavaScript with identical performance. The only overhead is the compile step during development.

Should I use TypeScript with a small script?

For a 20-line one-off, plain JavaScript is fine. For anything you will maintain, share, or extend, TypeScript pays for itself within the first hour of edits. Modern editors give type hints in JavaScript too via JSDoc, if you want a middle path.

Do I still need Babel?

Usually no in 2026. TypeScript compiles to modern JavaScript directly, and Vite / esbuild / swc handle the transpilation faster than Babel used to. Babel is still around for niche transforms (some legacy setups, custom AST plugins) but a standard project does not need it.

Related Modern Web Dev tutorials

  • Next.js 15 Complete Beginner Guide 2026 (First App)
  • React 19 vs React 18 Migration Guide 2026
  • Vue 3 Complete Beginner Guide 2026 (Composition API)
  • tRPC Tutorial 2026 (End-to-End TypeScript API) (coming this week)

Official documentation

Leave a Comment