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:
- Union-type array: an array where each element can be one of several types
- 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.
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
0must be a number - index
1must 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:
idis anumbernameis astring
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
- TypeScript sees this declaration:
let pair: [number, string]
This means the variable must contain:
- a number at index
0 - a string at index
1
- The assigned value is checked:
[1, "message"]
1matchesnumber"message"matchesstring
So the assignment is valid.
- When reading
pair[0]:
const first = pair[0];
TypeScript knows first is a number.
- 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.
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
| Type | Example | Meaning | Best Use Case |
|---|---|---|---|
| Union-type array | `(number | string)[]` | Any element can be a number or string |
| Tuple | [number, string] | First item is number, second is string | Fixed 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 positions | Clearer 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];
aallows mixed element types in any orderbrequires exactly two elements in a fixed ordercmust be all numbers or all strings, not mixed
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.
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
typeofcheck when working with the union-type array
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.