TypeScript har et rikt typesystem, men du trenger ikke forstå alt for å være produktiv. Virkeligheten er at 90% av arbeidet gjøres av en håndfull funksjoner du bruker hver dag. Dette er den praktiske referansen — primitiver, fagforeninger, generics, verktøytyper og innsnevringsmønstrene som får det hele til å klikke. Ingen abstrakt teori; hvert eksempel er den slags kode du skriver i et ekte prosjekt. Hvis du kan JavaScript godt og har gjort noe TypeScript, men vil styrke din mentale modell, er dette for deg. Den fullstendige historien er i TypeScript-håndboken — denne artikkelen er 80/20-versjonen.

Primitiver og literaler

TypeScripts primitive typer mapper direkte til JavaScripts kjøretidstyper: string, number, boolean, null, undefined, bigint og symbol. Du bruker de tre første konstant; resten dukker opp i spesifikke kontekster.

ts
// Primitives — the foundation
let userId: number = 42;
let username: string = 'alice';
let isActive: boolean = true;
let deletedAt: Date | null = null;     // nullable pattern
let refreshToken: string | undefined;  // optional pattern

Der TypeScript blir interessant er literaltyper. I stedet for bare å si at en verdi er en string, kan du si at den må være en av et spesifikt sett med strenger. Dette er langt mer nyttig enn en enkel string-annotasjon fordi TypeScript kan fange ugyldige verdier på kompileringstidspunktet:

ts
type Direction = 'north' | 'south' | 'east' | 'west';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type StatusCode = 200 | 201 | 400 | 401 | 403 | 404 | 500;

function navigate(dir: Direction) {
  console.log(`Moving ${dir}`);
}

navigate('north');  // ✅
navigate('up');     // ❌ Argument of type '"up"' is not assignable to type 'Direction'

// Combine with string for "known values plus free-form"
type EventName = 'click' | 'focus' | 'blur' | (string & {});
(string & {})-trikset til slutt holder autofullfør i gang for de kjente verdiene mens det fortsatt godtar enhver streng. Det er et vanlig mønster i designsystembiblioteker.

interface vs type — når bruker man hva

Dette er spørsmålet enhver TypeScript-nybegynner stiller. Det praktiske svaret: bruk interface for objektformer — spesielt de som representerer offentlige API-kontrakter eller som andre typer vil utvide. Bruk type for fagforeninger, skjæringer, kartlagte typer og alt som ikke utelukkende er en objektform. I praksis er de i stor grad utskiftbare for objektformer — velg én konvensjon og vær konsekvent innenfor en kodebase.

ts
// interface — object shapes, extensible via extends
interface User {
  id: number;
  email: string;
  createdAt: Date;
}

interface AdminUser extends User {
  role: 'admin';
  permissions: string[];
}

// type — unions, intersections, aliases for anything
type ID = string | number;
type Nullable<T> = T | null;

// Intersection — combine two shapes (common for mixins / HOC props)
type WithTimestamps = {
  createdAt: Date;
  updatedAt: Date;
};

type UserRecord = User & WithTimestamps;

// type is required here — interface can't express a union of shapes
type ApiResponse<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: string; code: number };

En meningsfull forskjell: grensesnitt støtter deklarasjonssammenslåing — du kan deklarere det samme grensesnittet to ganger og TypeScript slår sammen definisjonene. Slik utvider biblioteker globale typer (f.eks. å legge til egenskaper til Window). Typer slås ikke sammen; å re-deklarere en type er en feil.

Fagforenings- og skjæringstyper

Fagforeningstyper (A | B) sier "denne verdien er enten A eller B". Skjæringstyper (A & B) sier "denne verdien er både A og B på samme tid". Fagforeninger er overalt i ekte kode — det mest kraftfulle mønsteret de muliggjør er diskriminert fagforening, som er måten du modellerer data som kan være i forskjellige tilstander uten å ty til valgfrie felter overalt.

ts
// Discriminated union — model API response states cleanly
type FetchState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string; retryable: boolean };

// TypeScript narrows the type inside each branch
function render<T>(state: FetchState<T>) {
  switch (state.status) {
    case 'idle':    return '<p>Not started</p>';
    case 'loading': return '<p>Loading...</p>';
    case 'success': return `<pre>${JSON.stringify(state.data)}</pre>`;
    case 'error':   return `<p>Error: ${state.error}</p>`;
  }
}

// Intersection — common in React/Angular for composing prop types
type ButtonBaseProps = {
  label: string;
  disabled?: boolean;
};

type IconButtonProps = ButtonBaseProps & {
  icon: string;
  iconPosition: 'left' | 'right';
};

Det diskriminerte fagforenmønsteret er avhengig av en diskriminant — en literal egenskap (her status) som er unik for hver variant. TypeScript bruker den egenskapen til å innsnevre den fulle typen inne i betingede grener, noe som gir deg typesikker tilgang til variantspesifikke felter som data eller error.

Generics — delen som snubler alle

Det motiverende problemet: du vil ha en funksjon som fungerer på flere typer, men bruk av any kaster bort all typeinformasjon. Generics løser dette — de er typeparametere, skrevet i vinkelbraketter, som lar en funksjon eller et grensesnitt fungere over en familie av typer mens full typesikkerhet bevares.

ts
// Without generics — any loses all information
function first(arr: any[]): any {
  return arr[0];
}
const x = first([1, 2, 3]); // x is 'any' — TypeScript can't help you here

// With generics — T is inferred from the argument
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}
const n = first([1, 2, 3]);       // n: number | undefined ✅
const s = first(['a', 'b', 'c']); // s: string | undefined ✅

Syntaksen <T> deklarerer en typeparameter. Du kan navngi den hva som helst — T er bare konvensjonen for en enkelt generisk type. Når du kaller funksjonen, utleder TypeScript T fra argumentene, så du sjelden trenger å skrive det eksplisitt. Generiske grensesnitt er like nyttige for modellering av API-former:

ts
// Generic interface — the API wrapper pattern every codebase has
interface ApiResponse<T> {
  data: T;
  meta: {
    page: number;
    totalPages: number;
    totalItems: number;
  };
}

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

// The response type is fully typed — no casting needed
async function fetchUsers(): Promise<ApiResponse<User[]>> {
  const res = await fetch('/api/users');
  return res.json();
}

const response = await fetchUsers();
response.data[0].email; // ✅ TypeScript knows this is a string
response.meta.totalPages; // ✅ TypeScript knows this is a number

// Constrained generics — T must have an 'id' field
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

const user = findById(users, 42); // T inferred as User
Når man skal bruke generics: når du finner deg selv i å skrive den samme funksjonssignaturen to ganger med forskjellige typer, eller caster med as for å få returtypen du ønsker. Det er de to tegnene på at en generic ville vært renere.

Verktøytyper du faktisk vil bruke

TypeScript leveres med et sett av innebygde verktøytyper som transformerer eksisterende typer til nye. De eliminerer behovet for manuelt å duplisere eller justere typedefinisjoner. Her er de som dukker opp konstant i ekte kodebaser:

ts
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
  createdAt: Date;
}

// Partial<T> — all properties become optional
// Perfect for PATCH/update payloads
type UpdateUserPayload = Partial<User>;
// { id?: number; name?: string; email?: string; ... }

async function updateUser(id: number, payload: Partial<User>) {
  return fetch(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(payload)
  });
}

// Required<T> — all properties become required (opposite of Partial)
interface Config {
  apiUrl?: string;
  timeout?: number;
  retries?: number;
}
type ResolvedConfig = Required<Config>;
// { apiUrl: string; timeout: number; retries: number }

// Pick<T, K> — keep only specified properties
type UserSummary = Pick<User, 'id' | 'name' | 'email'>;
// Use in list views where you only need the display fields

// Omit<T, K> — exclude specified properties
type PublicUser = Omit<User, 'role' | 'createdAt'>;
// Safe to expose in API responses

// Readonly<T> — prevents mutation (great for config and frozen state)
const config: Readonly<ResolvedConfig> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
};
// config.timeout = 10000; // ❌ Cannot assign to 'timeout' because it is a read-only property

// Record<K, V> — typed dictionary
type UserCache = Record<number, User>;
const cache: UserCache = {};
cache[42] = { id: 42, name: 'Alice', email: '[email protected]', role: 'user', createdAt: new Date() };

// Common pattern: mapping string keys to a known value shape
type FeatureFlags = Record<string, { enabled: boolean; rolloutPct: number }>;

// ReturnType<T> — infer the return type of a function
function createSession(userId: number) {
  return {
    token: crypto.randomUUID(),
    userId,
    expiresAt: new Date(Date.now() + 86_400_000)
  };
}

type Session = ReturnType<typeof createSession>;
// { token: string; userId: number; expiresAt: Date }
// — no need to define the type separately and keep it in sync

unknown vs any vs never

Disse tre typene forvirrer de fleste utviklere til de forstår hvilket problem hver løser. any er rømningsluken — den deaktiverer typekontroll fullstendig for den verdien. Det er nyttig ved migrering av JavaScript til TypeScript, men overforbruk ødelegger formålet med TypeScript. unknown er det typesikre alternativet: verdien kan være hva som helst, men du må innsnevre den før du kan gjøre noe med den. never er en type som aldri kan forekomme — bunnen av typehierarkiet.

ts
// any — type checking disabled, use sparingly
function dangerousTransform(input: any) {
  return input.toUpperCase(); // TypeScript won't warn even if this crashes at runtime
}

// unknown — safe alternative, forces you to check before using
function safeTransform(input: unknown): string {
  if (typeof input === 'string') {
    return input.toUpperCase(); // ✅ narrowed to string inside this block
  }
  if (typeof input === 'number') {
    return input.toFixed(2);    // ✅ narrowed to number
  }
  throw new Error(`Cannot transform value of type ${typeof input}`);
}

// Common use: parsing untrusted data (API responses, localStorage, user input)
function parseConfig(raw: unknown): ResolvedConfig {
  if (
    typeof raw === 'object' &&
    raw !== null &&
    'apiUrl' in raw &&
    typeof (raw as any).apiUrl === 'string'
  ) {
    return raw as ResolvedConfig;
  }
  throw new Error('Invalid config format');
}

// never — the exhaustive switch pattern
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.radius ** 2;
    case 'square': return shape.side ** 2;
    default:
      // If you add a new Shape variant and forget to handle it here,
      // TypeScript will error: Type 'NewShape' is not assignable to type 'never'
      const _exhaustive: never = shape;
      throw new Error(`Unhandled shape: ${_exhaustive}`);
  }
}

Typeinnsnevring i praksis

Typeinnsnevring er hvordan TypeScript forfiner en bred type (string | number, unknown, en diskriminert fagforening) til en spesifikk type inne i en betinget blokk. TypeScript-innsnevringsdokumentasjonen dekker hver vakt i dybden — her er mønstrene du skriver daglig.

ts
// typeof — primitive narrowing
function formatValue(val: string | number | boolean): string {
  if (typeof val === 'string')  return val.trim();
  if (typeof val === 'number')  return val.toLocaleString();
  return val ? 'Yes' : 'No';
  // TypeScript knows val must be boolean here
}

// instanceof — class/object narrowing
function handleError(err: unknown): string {
  if (err instanceof Error)   return err.message;
  if (typeof err === 'string') return err;
  return 'An unknown error occurred';
}

// in operator — property existence check
type Cat = { meow(): void };
type Dog = { bark(): void };

function makeNoise(animal: Cat | Dog) {
  if ('meow' in animal) {
    animal.meow(); // TypeScript narrows to Cat
  } else {
    animal.bark(); // TypeScript narrows to Dog
  }
}

// Discriminated union narrowing (most common in component state)
type LoadState<T> =
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; message: string };

function renderState<T>(state: LoadState<T>) {
  if (state.status === 'success') {
    console.log(state.data);    // ✅ data is accessible
  } else if (state.status === 'error') {
    console.error(state.message); // ✅ message is accessible
  }
}

// Type predicates — custom type guards
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'email' in obj
  );
}

// After isUser() returns true, TypeScript knows obj is User
function processPayload(payload: unknown) {
  if (isUser(payload)) {
    console.log(payload.email); // ✅ fully typed
  }
}

typeof-operatoren er det mest grunnleggende innsnevringsverktøyet. Kombinert med instanceof, in-operatoren og diskriminantegenskapskontroller, kan du håndtere nesten ethvert innsnevringsscenario uten å ty til any eller usikre casts.

Oppsummering

TypeScripts typesystem belønner det å lære det grunnleggende godt fremfor å memorere obskure funksjoner. Literaltyper og diskriminerte fagforeninger eliminerer hele kategorier av kjøretidsfeil. Generics lar deg skrive gjenbrukbare, typesikre abstraksjoner. Verktøytyper som Partial, Pick, Omit og ReturnType holder typene dine DRY. Og unknown med innsnevring gir deg sikkerheten av typer selv ved kantene der data kommer inn fra omverdenen. Den beste måten å befeste alt dette på er å eksperimentere interaktivt — TypeScript Playground lar deg lime inn et vilkårlig kodebiter og se de utledede typene umiddelbart, ingen oppsett nødvendig. Når du jobber med JSON-data i TypeScript-prosjekter, kan JSON Formatter og JSON til TypeScript-verktøyene på dette nettstedet generere grensesnittdefinisjoner fra ekte API-nyttelaster automatisk.