TypeScript Basics #2: Basic Types

4 min read

Last time we covered what TypeScript is and why you’d use it, all the way through compiling and running your first piece of code. This time we go through the basic types you will use every day.

Primitive types — string / number / boolean #

These map one-to-one to JavaScript’s primitive types.

Primitive types
const name: string = '철수';
const age: number = 30;
const isAdmin: boolean = false;

TypeScript’s string, number, and boolean are all lowercase. Don’t confuse them with Java’s uppercase class types like String/Integer. TypeScript does have an uppercase String, but it’s almost never used; stick to the lowercase string.

number covers both integers and floats #

Just like JavaScript, number doesn’t distinguish between integers and floats.

number
const count: number = 42;
const pi: number = 3.14;
const big: number = 1_000_000;     // digit separator (JavaScript syntax)
const hex: number = 0xff;          // hexadecimal
const bin: number = 0b1010;        // binary

If you need very large integers, you can use the bigint type (100n), but it’s rarely used in everyday code.

null and undefined #

JavaScript has two kinds of “no value.” TypeScript follows the same.

null and undefined
const a: null = null;
const b: undefined = undefined;

These two types are rarely used on their own — they are usually combined with another type using a union (#4).

null or string
let user: string | null = null;
user = '철수';

Arrays #

There are two notations for array types.

Array types - two notations
const fruits: string[] = ['사과', '바나나', '체리'];
const numbers: Array<number> = [1, 2, 3, 4];

string[] and Array<string> mean exactly the same thing. The shorter [] form is the common choice; the generic form occasionally shows up because it reads more naturally for complex types.

If you try to put a different type into the array, TypeScript blocks it.

Wrong usage
const fruits: string[] = ['사과', 42];  // 🚫 number can't go into string[]

If you want to mix types, use a union (#4):

Mixed array
const mixed: (string | number)[] = ['사과', 42, '바나나'];

Tuples #

A tuple is a kind of array, but with a fixed type for each position.

Tuple
let point: [number, number] = [10, 20];
let labeled: [string, number] = ['age', 30];

point[0];  // number
point[1];  // number

labeled[0];  // string
labeled[1];  // number
labeled[2];  // 🚫 error: index 2 is not defined

The length is fixed and so is the type at each position. It works well for data where each position has a distinct meaning, like coordinates (x, y) or (key, value) pairs. It’s commonly used when a function needs to return multiple values.

useState-like pattern
function useCounter(initial: number): [number, () => void] {
  // ... (simplified)
  return [42, () => {}];
}

const [count, increment] = useCounter(0);

React’s useState uses exactly this tuple-return pattern.

Object types #

You can also describe the shape of an object.

Object type
const user: { name: string; age: number; isAdmin: boolean } = {
  name: '철수',
  age: 30,
  isAdmin: false,
};

When this inline object type gets long, readability suffers. It’s common to define it separately with interface or type — we cover that in detail in #3. For now, knowing the inline form is enough.

Optional properties #

Add a question mark and that property may be missing.

Optional
const user: { name: string; age?: number } = { name: '철수' };  // age missing is OK

readonly #

For read-only properties, prefix with readonly.

readonly
const user: { readonly id: string; name: string } = { id: 'u-1', name: '철수' };

user.name = '영희';  // ✓
user.id = 'u-2';     // 🚫 readonly, can't change

readonly is compile-time protection only — it isn’t true JavaScript-level immutability (Object.freeze). Even so, it’s very useful for catching unintended mutations.

enum #

A set of named constants.

enum
enum Color {
  Red,
  Green,
  Blue,
}

const c: Color = Color.Red;
console.log(c);  // 0

By default, enum members are automatically assigned integers starting from 0. You can also assign explicit values.

String enum
enum Status {
  Pending = 'PENDING',
  Active = 'ACTIVE',
  Deleted = 'DELETED',
}

const s: Status = Status.Active;  // 'ACTIVE'

An alternative to enum — Union of literals #

The TypeScript community tends to prefer literal unions (#4) over enums.

literal union instead of enum
type Status = 'pending' | 'active' | 'deleted';

const s: Status = 'active';

This is lighter and produces simpler compiled output (an enum generates an extra object at runtime). We cover it in detail in #4.

In this series, learn enums too, but prefer literal unions for new code.

any — turn off type checking #

The any type means “any type is allowed.”

any
let value: any = '문자열';
value = 42;
value = { name: '철수' };
value.foo.bar.baz;  // 🚫 may crash at runtime — compile passes

Declaring something as any makes TypeScript stop all checks on that variable. Anything can be assigned to it, anything can be called on it, and it behaves exactly like untyped JavaScript.

any is TypeScript’s escape hatch. Use it only when there’s truly no other choice; avoid it in everyday code. Use it too often and there’s no point in using TypeScript at all.

unknown — a safer any #

Like any, it means “any value can come in,” but it’s a safer type that forces type checks at the point of use.

unknown
let value: unknown = '문자열';

value.toUpperCase();  // 🚫 unknown — can't call methods

if (typeof value === 'string') {
  value.toUpperCase();  // ✓ narrowed to string inside the branch
}

When dealing with values whose type you don’t know — such as external API responses or user input — using unknown instead of any is safer. It always forces type narrowing (#4) before use.

void — no return value #

Used when a function doesn’t return a meaningful value.

void
function log(message: string): void {
  console.log(message);
}

void is similar to undefined but means something different. undefined means the function explicitly returns undefined, while void means the caller doesn’t care about the return value. It shows up often as the return type of callback functions.

never — never happens #

Used for functions that always throw or loop forever — that never return normally.

never
function fail(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // ...
  }
}

You rarely write it directly; it occasionally appears in type narrowing to signal that a branch is unreachable.

Type assertion #

When you know something the TypeScript compiler doesn’t, you can assert the type explicitly.

Type assertion
const input = document.getElementById('email') as HTMLInputElement;
input.value;  // ✓ HTMLInputElement has value

document.getElementById returns HTMLElement | null by default, but we know the element is an input tag, so we assert the type to narrow it.

The as Type form is standard. There’s an older style <HTMLInputElement>..., but it conflicts with JSX and is almost never used.

A type assertion partially bypasses type checking. It’s not as dangerous as any, but a wrong assertion can cause runtime errors. Only use it when you genuinely know better than the compiler.

A readable example #

A small example combining what we’ve covered so far.

Realistic code
// User data shape
const user: {
  readonly id: string;
  name: string;
  email: string;
  age?: number;
  roles: string[];
} = {
  id: 'u-1',
  name: '철수',
  email: 'cheolsu@example.com',
  roles: ['admin', 'editor'],
};

// Search result — coordinate and distance tuple
const result: [number, number, number] = [37.5, 127.0, 5.2];
const [lat, lng, distance] = result;

// Function — void return
function logUser(u: typeof user): void {
  console.log(`${u.name} (${u.email})`);
}

// Unknown external data
function processApiResponse(data: unknown): void {
  if (typeof data === 'object' && data !== null && 'name' in data) {
    console.log('이름:', (data as { name: string }).name);
  }
}

The inline object type makes the user part a bit long. The next post (#3) covers how to clean this up using interface and type.

Try it yourself #

Write the following in index.ts and compile it. A few errors are placed on purpose.

index.ts (find the mistakes)
let name: string = '철수';
name = 42;  // error 1?

const ages: number[] = [30, '서른', 28];  // error 2?

const point: [number, number] = [10, 20, 30];  // error 3?

const user: { id: string; age?: number } = {
  id: 'u-1',
  age: undefined,  // is this OK?
};

let value: unknown = '문자열';
console.log(value.toUpperCase());  // error 4?

Compile with npx tsc and check which line produces which error. Things stick better when you guess first and then verify. Don’t peek at the answer.

Wrap-up #

This post ran through the basic types you’ll use every day.

  • Primitives: string, number, boolean, null, undefined
  • Collections: T[], Array<T>, tuple [T1, T2]
  • Objects: inline { ... } (optional ?, readonly)
  • Groups: enum + literal union alternative
  • Special: any (escape hatch), unknown (safer any), void, never
  • Assertion: value as Type

The “object types get long” problem is what we solve next. In “TypeScript Basics #3: interface and type alias” we cover the two tools that let you name and reuse object and function shapes — interface and type alias. The differences between them and when to use which is a frequently confusing topic, so we’ll wrap it up in one post.

X