Question

How to Define an Array with Multiple Types in TypeScript

Question

I have an array like this:

[1, "message"]

How should I define its type in TypeScript?

Specifically, I want to understand how to type an array that contains values of different types, such as a number and a string.

Short Answer

By the end of this page, you will understand how TypeScript handles arrays with mixed value types, when to use a union-type array like (number | string)[], and when to use a tuple like [number, string]. You will also learn the practical difference between these two approaches, how they behave in real code, and how to avoid common typing mistakes.

Concept

In TypeScript, there are two common ways to describe an array containing multiple types:

  1. Union-type array: an array where each element can be one of several types
  2. Tuple: an array with a fixed length and fixed types at specific positions

For the value:

[1, "message"]

TypeScript could describe it in either of these ways depending on what you mean.

1. Union-type array

let values: (number | string)[] = [1, "message"];

This means:

  • the array can contain numbers or strings
  • elements can appear in any order
  • the array can have any length

So all of these are valid:

let values: (number | string)[] = [1, "message", 2, "hello"];

2. Tuple

let pair: [number, string] = [1, "message"];

This means:

  • the first element must be a number
  • the second element must be a string
  • the array must have exactly two elements

This is more precise when position matters.

Why this matters

Type safety helps TypeScript catch mistakes early. If you know an array is always a specific structure, use a tuple. If you only know that it contains multiple allowed types in any order or quantity, use a union-type array.

Choosing the right one makes your code:

  • easier to understand
  • safer to refactor
  • better documented for other developers

Mental Model

Think of these two types like different kinds of containers.

Union-type array: a basket

A union-type array is like a basket that can hold apples or oranges in any order.

(number | string)[]

You are saying:

  • every item must be either a number or a string
  • but you do not care which position each one is in

Tuple: a labeled tray

A tuple is like a tray with labeled slots:

  • slot 1: number
  • slot 2: string
[number, string]

You are saying:

  • the first position has a specific meaning
  • the second position has a specific meaning
  • the structure must stay fixed

If your data is really a structured record packed into an array, a tuple is usually the better fit.

Take Quiz

Syntax and Examples

Union-type array syntax

let items: (number | string)[] = [1, "message"];

This allows any number of elements, as long as each one is a number or a string.

let items: (number | string)[] = [1, "hello", 42, "world"];

Tuple syntax

let item: [number, string] = [1, "message"];

This means:

  • index 0 must be a number
  • index 1 must be a string

Valid:

let item: [number, string] = [1, "message"];

Invalid:

let item: [number, string] = ["message", 1];

TypeScript will reject that because the positions are wrong.

Example: choosing the right type

// Any mix of numbers and strings
let mixedList: (number | string)[] = [1, "a", 2, "b"];

// Exactly two values in a known order
let userEntry: [number, string] = [101, "Alice"];

Use mixedList when the array is just a collection. Use userEntry when each position means something specific.

Reading values from a tuple

let userEntry: [number, string] = [101, "Alice"];

const id = userEntry[0];
const name = userEntry[1];

Here, TypeScript knows:

  • id is a number
  • name is a string

That is one major advantage of tuples.

Step by Step Execution

Consider this tuple example:

let pair: [number, string] = [1, "message"];

const first = pair[0];
const second = pair[1];

Step by step

  1. TypeScript sees this declaration:
let pair: [number, string]

This means the variable must contain:

  • a number at index 0
  • a string at index 1
  1. The assigned value is checked:
[1, "message"]
  • 1 matches number
  • "message" matches string

So the assignment is valid.

  1. When reading pair[0]:
const first = pair[0];

TypeScript knows first is a number.

  1. When reading pair[1]:
const second = pair[1];

TypeScript knows second is a string.

Compare with a union-type array

let values: (number | string)[] = [1, "message"];
const firstValue = values[0];

Now firstValue is typed as:

number | string

TypeScript cannot guarantee which one it is, because union-type arrays do not fix positions.

That is the key difference: tuples preserve meaning by position.

Real World Use Cases

When to use a tuple

Tuples are useful when an array represents a fixed structure.

Examples:

  • Coordinate pairs
    let point: [number, number] = [10, 20];
    
  • Database row fragments
    let userSummary: [number, string] = [1, "Alice"];
    
  • Function return values
    function getStatus(): [number, string] {
      return [200, "OK"];
    }
    
  • Key-value pairs
    let entry: [string, number] = ["age", 30];
    

When to use a union-type array

Union-type arrays are useful when the array is a general list of allowed values.

Examples:

  • Mixed user input tokens
    let tokens: (string | number)[] = ["page", 2, "limit", 10];
    
  • Command arguments
    let args: (string | number)[] = ["retry", 3, "fast"];
    
  • Parsed data from simple scripts
    let values: (string | number)[] = [1, "warning", 2, "info"];
    

If the order and meaning of each position matter, use a tuple. If the array is just a list of allowed element types, use a union-type array.

Take Quiz

Real Codebase Usage

In real projects, developers often prefer the most precise type they can reasonably use.

Common pattern: tuples for structured return values

function parseLine(line: string): [number, string] {
  const parts = line.split(":");
  return [Number(parts[0]), parts[1]];
}

This is useful when a function always returns a fixed pair.

Common pattern: arrays of tuples

const users: [number, string][] = [
  [1, "Alice"],
  [2, "Bob"],
  [3, "Charlie"]
];

This means each item in the outer array is a tuple.

Common pattern: validation before use

With union-type arrays, developers often narrow types before using them:

const values: (number | string)[] = [1, "hello"];

for (const value of values) {
  if (typeof value === "number") {
    console.log(value.toFixed(2));
  } else {
    console.log(value.toUpperCase());
  }
}

Because each element could be either type, checking is required.

Common pattern: prefer objects when meaning is unclear

Sometimes developers start with tuples but switch to objects for readability:

const user = {
  id: 1,
  name: "Alice"
};

This is often easier to understand than:

const user: [number, string] = [1, "Alice"];

So in real codebases:

  • use tuples for short, fixed, positional data
  • use objects when named fields improve clarity
  • use union arrays for flexible mixed lists

Common Mistakes

1. Using number[] | string[] when you mean mixed elements

Broken code:

let values: number[] | string[] = [1, "message"];

Why it is wrong:

  • number[] | string[] means either an array of only numbers or an array of only strings
  • it does not mean one array containing both

Correct:

let values: (number | string)[] = [1, "message"];

2. Using a union array when you actually need fixed positions

let pair: (number | string)[] = [1, "message"];

This works, but it is less specific than:

let pair: [number, string] = [1, "message"];

If position matters, use a tuple.

3. Putting tuple values in the wrong order

Broken code:

let pair: [number, string] = ["message", 1];

Correct:

let pair: [number, string] = [1, "message"];

4. Assuming a union-type array gives precise index types

let values: (number | string)[] = [1, "message"];
const first = values[0];

first is not guaranteed to be a number. Its type is:

number | string

If you need exact per-position types, use a tuple.

5. Overusing tuples when objects are clearer

This works:

let user: [number, string, boolean] = [1, "Alice", true];

But this is often clearer:

let user = {
  id: 1,
  name: "Alice",
  isActive: true
};

If the meaning of positions is hard to remember, an object may be better.

Comparisons

TypeExampleMeaningBest Use Case
Union-type array`(numberstring)[]`Any element can be a number or string
Tuple[number, string]First item is number, second is stringFixed structure with known positions
Array union`number[]string[]`Entire array is either all numbers or all strings
Object{ id: number, message: string }Named fields instead of positionsClearer structured data

Quick comparison example

let a: (number | string)[] = [1, "message", 2];
let b: [number, string] = [1, "message"];
let c: number[] | string[] = [1, 2, 3];
  • a allows mixed element types in any order
  • b requires exactly two elements in a fixed order
  • c must be all numbers or all strings, not mixed
Take Quiz

Cheat Sheet

Mixed array types in TypeScript

// Flexible array with mixed allowed types
let values: (number | string)[] = [1, "message"];

// Fixed two-element tuple
let pair: [number, string] = [1, "message"];

Use this when...

  • Use (number | string)[] when:

    • the array can have many elements
    • order is not important for typing
    • each element can be either type
  • Use [number, string] when:

    • the array always has exactly two items
    • each position has a specific type

Important difference

number[] | string[]

means:

  • either all numbers
  • or all strings

It does not mean mixed elements.

Type results

const arr: (number | string)[] = [1, "x"];
const value = arr[0]; // number | string

const tuple: [number, string] = [1, "x"];
const first = tuple[0]; // number
const second = tuple[1]; // string

Rule of thumb

  • Flexible list -> union-type array
  • Fixed structure -> tuple
  • Named data -> object

FAQ

How do I type an array with both numbers and strings in TypeScript?

Use a union-type array:

let values: (number | string)[] = [1, "message"];

How do I type exactly [number, string] in TypeScript?

Use a tuple:

let pair: [number, string] = [1, "message"];

What is the difference between (number | string)[] and [number, string]?

(number | string)[] is a flexible array where any element can be a number or a string. [number, string] is a tuple with exactly two items in a fixed order.

Is number[] | string[] the same as (number | string)[]?

No.

  • number[] | string[] means the entire array is all numbers or all strings.
  • (number | string)[] means a single array can contain both.

When should I use a tuple instead of an object?

Use a tuple when the data is short, fixed in length, and position is meaningful. Use an object when named fields make the code easier to read.

Can a tuple have more than two elements?

Yes.

let record: [number, string, boolean] = [1, "Alice", true];

Does TypeScript infer tuples automatically?

Sometimes, but not always in the way you expect. If you want a fixed tuple type, it is often clearer to declare it explicitly.

Take Quiz

Mini Project

Description

Build a small TypeScript example that stores status results in two ways: as a flexible mixed-value array and as a fixed tuple. This helps you see when each type is appropriate and how TypeScript treats them differently when you read values.

Goal

Create and use both a union-type array and a tuple, then observe how TypeScript types the values you read from them.

Requirements

  • Create one variable typed as (number | string)[]
  • Create one variable typed as [number, string]
  • Add valid values to both variables
  • Read values from each variable and log them
  • Use a typeof check when working with the union-type array
Take Quiz

Keep learning

Related questions

View more