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
}
Prefer unknown 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:
- Functions that always throw an error
- Functions with infinite loops
- 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:
-
Primitives —
string,number,booleancover most cases - Type inference — TypeScript is smart; you don't need to annotate everything
-
Arrays —
string[]orArray<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.
Top comments (0)