TypeScriptBasic Types Overview

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.

TS
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;
Note
Use single quotes or template literals for strings — avoid double quotes (it is a common TypeScript/ESLint style convention, though not a language rule).
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.

TS
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;
}
Tip
If you need to work with very large integers without precision loss, use 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.

TS
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:

TS
// 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
Success
Always annotate function parameters and return types explicitly. For local variables, let TypeScript infer — writing redundant annotations adds noise without benefit.
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.

TS
// 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
Warning
Without 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."

TS
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!
Warning
Using 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.

TS
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#.

TS
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);
};
Note
A function with no return annotation also infers 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.

TS
// 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.

TS
// 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.

TS
// 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:

TS
// 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.

TS
// 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
Warning
Type assertions and the ! 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:

TS
// 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