← thecodex.expert · The Codex Family of Knowledge
TypeScript

Basic Types

any silently turns off type-checking for a value; unknown demands you prove what it is before you can use it — they look similar and behave completely differently.

TypeScript 6.0 any vs unknown Last verified:
Where this bites you

Type annotations are erased before the code runs — they describe what you expect, they never check what actually arrives. JSON.parse returns any, so annotating its result as User compiles with no complaint even when the data doesn't match. Here the server sends age as a string, TypeScript still believes it's a number, and + 1 quietly concatenates instead of adding.

TypeScriptjson_bug.ts
type User = { name: string; age: number };

const body = '{"name": "Alice", "age": "30"}';   // age arrives as a string
const user: User = JSON.parse(body);              // JSON.parse returns any

console.log(user.age + 1);
// tsc --strict: no errors
// node: 301      (string "30" + 1, not 31)
Canonical Definition

TypeScript's basic types are mostly JavaScript's own primitives with type annotations: string, number, boolean, plus arrays (string[]) and tuples (fixed-length, typed arrays). any opts a value out of type-checking entirely — TypeScript will not complain no matter how it's used, effectively reverting to plain JavaScript for that value. unknown is safer: it also accepts any value, but TypeScript refuses to let you use it for anything until you've narrowed its type with a runtime check.

Primitives, arrays, and tuples

A tuple's length and per-position types are both fixed — distinct from an array, whose elements are all the same type but can be any length.

TypeScriptbasics.ts
let userName: string = "Alice";
let age: number = 30;
let active: boolean = true;

let scores: number[] = [90, 85, 78];       // array: any length, all numbers
let point: [number, number] = [3, 4];       // tuple: exactly 2 numbers, fixed positions

any: opting out of type-checking

any is sometimes necessary (working with untyped legacy code, or a genuinely dynamic value), but it silently disables the type-checker's protection for anything it touches — a common source of bugs that TypeScript should have caught.

TypeScriptany_type.ts
let data: any = fetchExternalData();
data.someProperty.deeplyNested.thatMayNotExist();
// no compile error at all — any means TypeScript checks NOTHING here

unknown: the type-safe alternative to any

unknown accepts any value just like any, but TypeScript refuses to let you call a method or access a property on it until you've proven its actual type with a runtime check — this is the type-safe default for genuinely unknown external data.

TypeScriptunknown_type.ts
let data: unknown = fetchExternalData();

// data.someProperty;   // COMPILE ERROR: object is of type 'unknown'

if (typeof data === "object" && data !== null && "someProperty" in data) {
    console.log((data as { someProperty: string }).someProperty);   // now safe
}

That's the any problem from the section above arriving through a side door: you never wrote any, but JSON.parse did. The fix is the same as the page's own advice — treat outside data as unknown and check it before it is allowed to become a User:

TypeScriptjson_fix.ts
function parseUser(text: string): User {
    const raw: unknown = JSON.parse(text);
    if (typeof raw !== "object" || raw === null) throw new Error("not an object");
    const r = raw as Record<string, unknown>;
    const age = typeof r.age === "string" ? Number(r.age) : r.age;
    if (typeof r.name !== "string" || typeof age !== "number" || Number.isNaN(age)) {
        throw new Error("invalid user");
    }
    return { name: r.name, age };
}

const user = parseUser(body);
console.log(user.age + 1);   // 31

Sources

1
Microsoft. TypeScript Handbook, "The Basics" and "Everyday Types", typescriptlang.org/docs/handbook.