Basic Types Overview
TypeScript's type system is built on a small set of fundamental types that map directly to JavaScript's primitive values, plus a few extras that TypeScript adds on top. Mastering these basic types is the foundation for everything else in TypeScript.
The Primitive Types
JavaScript has seven primitive types. TypeScript has first-class support for all of them:
TypeScript Type | JavaScript typeof | Example Values |
|---|---|---|
string | "string" | "hello", "world", template literals |
number | "number" | 42, 3.14, NaN, Infinity |
boolean | "boolean" | true, false |
bigint | "bigint" | 9007199254740991n |
symbol | "symbol" | Symbol("id") |
null | "object" (JS quirk) | null |
undefined | "undefined" | undefined |
string
Represents text data. All three JavaScript string literal styles are valid.
let firstName: string = 'Alice';
let lastName: string = "Smith";
let greeting: string = `Hello, ${firstName} ${lastName}!`;
// Common string operations retain their types
const upper: string = firstName.toUpperCase(); // string
const len: number = firstName.length; // number
// Error: number is not assignable to string
firstName = 42;number
TypeScript uses a single number type for all numeric values — integers, floats, hex, octal, binary, NaN, and Infinity. There is no separate int type.
let decimal: number = 42;
let float: number = 3.14159;
let hex: number = 0xff; // 255
let binary: number = 0b1010; // 10
let octal: number = 0o17; // 15
let notANumber: number = NaN;
let inf: number = Infinity;
// Arithmetic is fully typed
function area(width: number, height: number): number {
return width * height;
}bigint (e.g., const big: bigint = 9007199254740993n). Regular number loses precision above 2^53 - 1.boolean
Represents logical true/false values. TypeScript's boolean type only accepts true or false — unlike JavaScript, truthy/falsy values like 1, "", or null are not assignable to boolean.
let isActive: boolean = true;
let isLoggedIn: boolean = false;
// Common patterns
function toggle(value: boolean): boolean {
return !value;
}
// Type narrowing with boolean
function displayStatus(active: boolean) {
if (active) {
console.log('System is running');
} else {
console.log('System is stopped');
}
}Type Inference for Primitives
You rarely need to write primitive type annotations for local variables — TypeScript infers them from the initial value. The following pairs are equivalent:
// Explicit annotation (verbose — unnecessary for locals) let name: string = 'Alice'; let age: number = 30; let active: boolean = true; // Inferred (preferred — TypeScript figures it out) let name = 'Alice'; // TypeScript knows: string let age = 30; // TypeScript knows: number let active = true; // TypeScript knows: boolean
null and undefined
In TypeScript with strictNullChecks enabled (which you should always enable), null and undefined are their own separate types, not assignable to other types. This prevents the infamous "Cannot read property of null" errors.
// With strictNullChecks: true (recommended) let name: string = 'Alice'; name = null; // Error: Type 'null' is not assignable to type 'string' name = undefined; // Error: Type 'undefined' is not assignable to type 'string' // To allow null, use a union type let nullable: string | null = 'Alice'; nullable = null; // OK // undefined is common for optional values let optional: string | undefined; optional = 'hello'; // OK optional = undefined; // OK
strictNullChecks, null and undefined are assignable to every type — this makes TypeScript far less useful. Always enablestrict: true in your tsconfig.json.any
any is a special escape hatch that turns off type checking for a value. It tells TypeScript "I don't know the type and I don't want you to check it."
let value: any = 42;
value = 'hello'; // OK — any accepts everything
value = true; // OK
value = { x: 1 }; // OK
// Danger: TypeScript trusts you completely
value.nonExistent.deeply.nested; // No error — but crashes at runtime!
value(); // No error — but crashes at runtime!any disables TypeScript's protection. Avoid it whenever possible. When you need to express "I don't know the type yet," prefer unknown instead — it is the safe alternative.unknown
unknown is the type-safe counterpart to any. It accepts any value, but TypeScript requires you to narrow the type before using it.
let value: unknown = fetchDataFromAPI();
// Error: Object is of type 'unknown'
console.log(value.name);
// Correct: narrow the type first
if (typeof value === 'string') {
console.log(value.toUpperCase()); // OK — TypeScript knows it's string here
}
if (typeof value === 'object' && value !== null && 'name' in value) {
console.log((value as { name: string }).name); // OK
}void
void is used as the return type of functions that do not return a value. It is analogous to void in C, Java, or C#.
function logMessage(message: string): void {
console.log(message);
// No return statement needed
}
// void means "the caller should not use the return value"
const result = logMessage('hello');
// result is void — you can't do anything meaningful with it
// Arrow function equivalent
const log = (msg: string): void => {
console.log(msg);
};void when it has no return statement. Annotating it explicitly is a good habit for public API functions to signal intent clearly.never
never represents values that never occur. It is used for functions that always throw an error or never return (infinite loops), and for exhaustiveness checking in discriminated unions.
// Function that always throws
function throwError(message: string): never {
throw new Error(message);
}
// Function that never terminates
function infiniteLoop(): never {
while (true) {
// ...
}
}
// Exhaustiveness checking — catch missing cases at compile time
type Shape = 'circle' | 'square' | 'triangle';
function getArea(shape: Shape): number {
switch (shape) {
case 'circle': return Math.PI * 5 * 5;
case 'square': return 5 * 5;
case 'triangle': return (5 * 5) / 2;
default:
// If Shape gets a new member, TypeScript errors here
const _exhaustive: never = shape;
throw new Error(`Unhandled shape: ${shape}`);
}
}object
The object type refers to anything that is not a primitive (not a string, number, boolean, bigint, symbol, null, or undefined). It is rarely used directly — you will almost always prefer a more specific object type or interface.
// object accepts any non-primitive
let obj: object = { x: 1 };
obj = [1, 2, 3]; // arrays are objects — OK
obj = () => {}; // functions are objects — OK
obj = 'hello'; // Error: string is a primitive
// Rarely useful — prefer a specific shape
function processObject(o: object) {
// Cannot access o.name — object has no known properties
// Better: use Record<string, unknown> or an interface
}Literal Types
TypeScript can narrow a type down to a specific literal value. Literal types make excellent building blocks for union types that act like enums.
// String literal types
type Direction = 'north' | 'south' | 'east' | 'west';
type Status = 'pending' | 'active' | 'inactive';
// Numeric literal types
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
// Boolean literal (rarely used alone, but valid)
type AlwaysTrue = true;
function move(direction: Direction) {
console.log(`Moving ${direction}`);
}
move('north'); // OK
move('diagonal'); // Error: not a valid Direction
// const narrows to a literal type
const dir = 'north'; // type: 'north' (literal)
let dir2 = 'north'; // type: string (widened)Type Widening and Narrowing
TypeScript widens types in certain contexts and narrows them in others. Understanding this is key to getting TypeScript to cooperate:
// Widening: let widens literals to their base type
let x = 'hello'; // string (widened from 'hello')
const y = 'hello'; // 'hello' (literal — const cannot be reassigned)
// Narrowing: typeof checks restrict the type
function process(value: string | number) {
if (typeof value === 'string') {
value.toUpperCase(); // TypeScript knows: string
} else {
value.toFixed(2); // TypeScript knows: number
}
}
// Narrowing: truthiness
function greet(name: string | null) {
if (name) {
console.log(`Hello, ${name}`); // name is string here (null is falsy)
} else {
console.log('Hello, stranger');
}
}Type Assertions
Sometimes you know more about a type than TypeScript can infer. Type assertions let you override TypeScript's inference — but use them with care, as they can mask real bugs.
// as syntax (preferred)
const input = document.getElementById('username') as HTMLInputElement;
console.log(input.value); // OK — TypeScript treats it as HTMLInputElement
// angle-bracket syntax (not valid in .tsx files)
const input2 = <HTMLInputElement>document.getElementById('username');
// Non-null assertion operator (!)
// Tells TypeScript: "I know this is not null or undefined"
const el = document.getElementById('root')!;
el.innerHTML = '<p>Hello</p>'; // OK — we asserted it is not null! non-null assertion operator bypass TypeScript's checks. Use them only when you are genuinely certain about the type — and document why. Overusing them defeats the purpose of TypeScript.The const Assertion
as const tells TypeScript to treat the entire value as deeply immutable and infer the narrowest possible type:
// Without as const: TypeScript widens to string[]
const colors = ['red', 'green', 'blue'];
// type: string[]
// With as const: TypeScript infers the literal tuple type
const colorsConst = ['red', 'green', 'blue'] as const;
// type: readonly ['red', 'green', 'blue']
// Useful for configuration objects
const config = {
host: 'localhost',
port: 3000,
env: 'development',
} as const;
// type: { readonly host: 'localhost'; readonly port: 3000; readonly env: 'development' }
type Env = typeof config.env; // 'development' (literal, not string)Quick Reference: Type Summary
Type | Use For | Notes |
|---|---|---|
string | Text values | Use template literals for interpolation |
number | All numeric values | No separate int/float/double |
boolean | true / false | Not truthy/falsy — only literal true/false |
bigint | Very large integers | Use n suffix: 100n |
symbol | Unique identifiers | Rarely needed in everyday code |
null | Intentional absence of value | Enable strictNullChecks always |
undefined | Uninitialized / optional | Default for missing object properties |
any | Escape hatch — avoid | Disables type checking entirely |
unknown | Truly unknown input | Safe alternative to any — requires narrowing |
void | Function return with no value | Signals "do not use the return value" |
never | Unreachable / always-throw functions | Also used for exhaustiveness checks |
Summary
string, number, and boolean are the three most common primitive types
TypeScript infers primitive types from initial values — avoid redundant annotations on local variables
Enabling strictNullChecks makes null and undefined their own distinct types
Prefer unknown over any when you need to accept values of any type
void marks functions that do not return a meaningful value
never is used for functions that never return and for exhaustiveness checks
Literal types narrow a type to a specific value — useful for union enums
as const makes the entire value deeply readonly with narrowest literal types