External Publication
Visit Post

TypeScript Types Demystified: Simple Types, Special Types, and Type Inference

DEV Community [Unofficial] June 19, 2026
Source

TypeScript Types Demystified: Simple Types, Special Types, and Type Inference

In the first post, we covered why TypeScript exists and how to write your first program. Now it's time to get comfortable with the type system itself — the foundation everything else is built on.

By the end of this post, you'll know how to type variables, arrays, and function parameters correctly. You'll also understand the "special" types that trip up most beginners: any, unknown, never, and void.

The Core Primitive Types

TypeScript's basic types map directly to JavaScript's primitives:

// string
let firstName: string = "Ramesh";
let greeting: string = `Hello, ${firstName}`;

// number (no separate int/float — it's all number)
let age: number = 31;
let price: number = 9.99;
let hex: number = 0xFF;

// boolean
let isLoggedIn: boolean = true;
let hasAccess: boolean = false;

These are the types you'll use most often. Simple, predictable, and exactly what you'd expect.

Type Inference: TypeScript Does the Work

You don't always have to write the type. TypeScript infers it from the value you assign:

let city = "Chennai";    // TypeScript infers: string
let year = 2026;         // TypeScript infers: number
let isActive = true;     // TypeScript infers: boolean

Once inferred, that type is locked in:

let city = "Chennai";
city = 42; // ❌ Error: Type 'number' is not assignable to type 'string'

Rule of thumb: Let TypeScript infer types for local variables. Write explicit annotations for function parameters and return types.

// Let inference work for variables
const scores = [95, 87, 72]; // inferred as number[]

// Be explicit for function signatures
function calculateAverage(scores: number[]): number {
  return scores.reduce((a, b) => a + b, 0) / scores.length;
}

Explicit vs Inferred — When to Choose Each

// ✅ Explicit annotation — good for function params & return types
function formatName(first: string, last: string): string {
  return `${first} ${last}`;
}

// ✅ Inferred — good for simple variable assignments
const result = formatName("Ramesh", "Kumar"); // inferred as string

// ❌ Over-annotating — redundant when value makes it obvious
const count: number = 5; // The `= 5` already tells TypeScript it's a number

Arrays and Tuples

Arrays hold multiple values of the same type:

// Two equivalent syntaxes
let tags: string[] = ["typescript", "javascript", "react"];
let scores: Array<number> = [95, 87, 72];

// TypeScript will catch wrong types in arrays
tags.push(42); // ❌ Error: Argument of type 'number' is not assignable to type 'string'

Tuples are fixed-length arrays where each position has a specific type:

// A tuple: exactly [string, number]
let user: [string, number] = ["Ramesh", 31];

// Order and types are both enforced
let wrongOrder: [string, number] = [31, "Ramesh"]; // ❌ Error

// Accessing tuple elements
console.log(user[0]); // "Ramesh" — TypeScript knows this is string
console.log(user[1]); // 31 — TypeScript knows this is number

Tuples are great for things like coordinate pairs, key-value pairs, or when returning multiple values from a function:

function getMinMax(numbers: number[]): [number, number] {
  return [Math.min(...numbers), Math.max(...numbers)];
}

const [min, max] = getMinMax([3, 1, 7, 2, 9]);
// min: number, max: number — fully typed!

Union Types: When a Value Can Be Multiple Types

Sometimes a value can legitimately be more than one type. That's what union types handle:

// This function accepts string OR number
function formatId(id: string | number): string {
  return `ID-${id}`;
}

formatId("abc123"); // ✅
formatId(42);       // ✅
formatId(true);     // ❌ Error: boolean not in the union

Union types are especially useful for API responses, form inputs, and optional values:

// A status that can only be one of these three strings
let orderStatus: "pending" | "shipped" | "delivered";

orderStatus = "shipped";   // ✅
orderStatus = "cancelled"; // ❌ Error: not in the union

// A value that might not exist yet
let userId: number | null = null;
userId = 101; // Fine, it's assigned now

Special Types: any, unknown, never, void

These four confuse most beginners. Here's each one explained clearly.

any — The Escape Hatch (Use Sparingly)

any turns off TypeScript's type checking for that value:

let data: any = "hello";
data = 42;        // Fine
data = true;      // Fine
data.foo.bar();   // Fine — no error, even if this blows up at runtime!

When to use it: Almost never. any defeats the purpose of TypeScript. It's there as a last resort when migrating old JavaScript code.

// ❌ Overusing any — you've lost all type safety
function processData(input: any): any { ... }

// ✅ Be specific whenever possible
function processData(input: string[]): number { ... }

unknown — The Safe Version of any

unknown says "this could be anything, but I need to check before I use it":

let userInput: unknown = getUserInput();

// ❌ Can't use unknown directly
console.log(userInput.toUpperCase()); // Error!

// ✅ Must narrow the type first
if (typeof userInput === "string") {
  console.log(userInput.toUpperCase()); // Now it's safe
}

Preferunknown over any when you genuinely don't know the type. It forces you to handle the uncertainty explicitly.

void — Functions That Return Nothing

Use void when a function doesn't return a value:

function logMessage(message: string): void {
  console.log(message);
  // No return statement needed
}

// Trying to use the return value of a void function makes no sense
const result = logMessage("hello"); // result is void — useless

never — Code That Never Completes

never represents values that never occur. It's used for:

  1. Functions that always throw an error
  2. Functions with infinite loops
  3. Exhaustive type checking
// Always throws — never returns
function throwError(message: string): never {
  throw new Error(message);
}

// Exhaustive check — if you add a new status and forget to handle it,
// TypeScript will error here
function handleStatus(status: "active" | "inactive"): string {
  if (status === "active") return "User is active";
  if (status === "inactive") return "User is inactive";

  // This line should be unreachable
  const _exhaustive: never = status; // ❌ Error if you missed a case
  return _exhaustive;
}

never is an advanced concept — don't worry if it doesn't fully click yet. You'll encounter it naturally as you write more TypeScript.

Quick Reference: All the Types We Covered

// Primitives
let name: string = "Ramesh";
let age: number = 31;
let active: boolean = true;

// Arrays
let tags: string[] = ["ts", "js"];
let ids: number[] = [1, 2, 3];

// Tuples
let point: [number, number] = [10, 20];

// Union
let id: string | number = "abc";
let status: "on" | "off" = "on";

// Special types
let anything: any = "...";     // Avoid when possible
let safeAny: unknown = "...";  // Safer — must narrow before use
function log(): void { ... }   // No return value
function fail(): never { throw new Error(); } // Never returns

Wrapping Up

The TypeScript type system is deep, but the everyday usage is straightforward. Here's what to take away:

  • Primitivesstring, number, boolean cover most cases
  • Type inference — TypeScript is smart; you don't need to annotate everything
  • Arraysstring[] or Array<string>, your choice
  • Tuples — fixed-length, fixed-type arrays
  • Union types — when a value can be more than one type
  • any — avoid it; unknown — use this instead when you must
  • void — functions with no return; never — functions that never return normally

Found this helpful? Follow for the rest of the series. Questions or corrections? Drop them in the comments.

Discussion in the ATmosphere

Loading comments...