Core TypeScript Data Types and Type System Patterns

Primitive and Basic Types

TypeScript extends JavaScript with static type annotations. While type inference automatically deduces types from initialization, explicit annotations enforce contracts and improve tooling support.

const maxRetries: number = 5;
const isEnabled: boolean = true;
const apiEndpoint: string = "https://api.example.com/v2";

Object Shapes: Interfaces vs Type Aliases

Both interface and type define object structures, but they differ in extensibility. Interfaces support declaration merging, making them ideal for augmenting existing types or library definitions. Type aliases provide broader flexibility, supporting unions, intersections, and conditional types.

interface DatabaseConfig {
  host: string;
  port: number;
  ssl?: boolean;
  connect(): Promise<void>;
  [metadata: string]: unknown;
}

type PartialConfig = {
  host?: string;
  timeout?: number;
};

interface SecureConfig extends DatabaseConfig {
  certPath: string;
}

Collections: Arrays and Tuples

Arrays are typically homogeneous and can be declared using shorthand brackets or the generic Array<T> syntax. Tuples enforce fixed-length arrays where each index maps to a specific type, preserving positional type safety.

type RecordEntry = {
  refId: number;
  description: string;
  archived: boolean;
};

const activeRecords: RecordEntry[] = [{ refId: 101, description: "Init", archived: false }];
const mixedMetrics: Array<number | string> = [500, "limit", 1024];

// Tuple: strict order and length
const locationData: [number, number, string] = [34.0522, -118.2437, "Los Angeles"];

Type Composition: Unions and Intersections

Union types (|) indicate a value may conform to one of several shapes. Intersection types (&) merge multiple types into a single structure, requiring all constraints to be satisfied simultaneously. Intersecting conflicting primitive types resolves to never, while object properties combine seamlessly.

interface Writer {
  accessLevel: string;
  publish(): void;
}

interface Reader {
  accessLevel: string;
  stream(): void;
}

function handleAccess(role: Writer | Reader) {
  // Only 'accessLevel' is safely accessible here
}

function elevatePermissions(role: Writer & Reader) {
  // Both publish() and stream() are guaranteed
}

Enumerations

Enums define a set of named constants, replacing magic numbers with readable identifiers. They support numeric auto-incrementation or explicit string mappings.

enum ResponseCode {
  SUCCESS = 200,
  NOT_FOUND = 404,
  TIMEOUT = 408,
  INTERNAL_ERROR = 500
}

enum WorkflowStatus {
  QUEUED = "queued",
  PROCESSING = "processing",
  COMPLETED = "completed"
}

Generics

Generics parameterize types, enabling reusable logic that maintains type information across different data structures. Common conventions use T for primary types, K for keys, and V for values.

function wrapInEnvelope<T>(payload: T): { content: T; createdAt: number } {
  return { content: payload, createdAt: Date.now() };
}

function combineMetadata<T extends Record<string, unknown>, U extends Record<string, unknown>>(
  base: T,
  extensions: U
): T & U {
  return { ...base, ...extensions };
}

Type Assertions and Runtime Guards

When compiler context is insufficient, developers can assert types using the as keyword, which is required in TSX/JSX syntax. The in operator performs runtime property checks, serving as a type guard to narrow unions safely.

interface ServerResponse {
  statusCode: number;
  body: unknown;
}

function processPayload(raw: any) {
  const typed = raw as ServerResponse;
  if ("body" in typed) {
    console.log(typed.body);
  }
}

Special Return Types: void and never

Functions that complete without returning data are typed as void. The never type represents unreachable code, such as functions that unconditionally throw exceptions or contain infinite loops.

function recordAudit(action: string): void {
  console.log(`[AUDIT] ${action}`);
}

function haltExecution(reason: string): never {
  throw new Error(`System failure: ${reason}`);
}

Type Extraction and Inspection

TypeScript provides operators to derive or validate types dynamically:

  • typeof: Extracts the type of a variable or expression.
  • keyof: Produces a union of all property keys from an object type.
  • instanceof: Narrows types by checking prototype chains at runtime.
interface Metric {
  identifier: number;
  label: string;
}

const baseline: Metric = { identifier: 1, label: "default" };
type MetricShape = typeof baseline; // Resolves to Metric

type MetricKeys = keyof Metric; // "identifier" | "label"

function isMetric(value: unknown): value is Metric {
  return typeof value === "object" && value !== null && "identifier" in value;
}

Class Access Modifiers and Structural Keywords

TypeScript enhances JavaScript classes with visibility controls and structural constraints:

  • public: Default accessibility, exposed everywhere.
  • private: Restricts access to the defining class only.
  • protected: Allows access within the class and its inheritance chain.
  • readonly: Locks properties after initial assignment.
  • static: Attaches methods or properties to the constructor rather than instances.
  • abstract: Defines base classes and enforces method implementation in subclasses.
abstract class CacheEngine {
  protected configUrl: string;

  constructor(url: string) {
    this.configUrl = url;
  }

  static initialize(url: string): CacheEngine {
    throw new Error("Instantiate a concrete cache driver");
  }

  abstract retrieve(key: string): Promise<string | null>;
}

Managing Unverified or Dynamic Data

Integrating with untyped modules or legacy code often leads to the use of any, which disables static checking. A safer practice is unknown, which forces explicit type narrowing before usage. Suppression directives like // @ts-ignore or // @ts-nocheck can silence errors for specific lines or entire files, but they should be applied sparingly to preserve type integrity.

// unknown enforces runtime validation before usage
const legacyPayload: unknown = requestOldService();

if (typeof legacyPayload === "object" && legacyPayload !== null && "identifier" in legacyPayload) {
  // TypeScript recognizes 'identifier' exists
  console.log(legacyPayload.identifier);
}

Tags: TypeScript type-system javascript frontend-development programming

Posted on Sun, 10 May 2026 01:29:36 +0000 by bdurham