Typing Function Parameters as an Array in TypeScript
Introduction
Working with arrays is a daily task for any JavaScript or TypeScript developer. Functions often accept arrays as inputs — lists of IDs, records, options, callbacks, or mixed data. While JavaScript leaves array contents untyped, TypeScript gives you the tools to specify shapes precisely, making your code safer, easier to maintain, and friendlier for editor tooling and refactoring.
In this article you'll learn how to type function parameters that are arrays in TypeScript across many realistic scenarios. We'll cover simple homogeneous arrays (number[], string[]), tuples (fixed-length arrays), readonly arrays, generics for flexible APIs, rest parameters vs explicit array parameters, typed transforms (map/reduce), union and discriminated unions within arrays, runtime validation tips, and integration with common ecosystems such as Express and React. Along the way you'll see actionable code snippets, step-by-step explanations, and troubleshooting tips so you can apply these patterns in real codebases.
By the end you'll be able to:
- Choose the right array type for intent (mutable, readonly, fixed-length).
- Use generics to write reusable functions that accept arrays of various element types.
- Combine arrays with tuple types and mapped types for precise contracts.
- Avoid common typing pitfalls like overly-wide types or incorrect inference.
If you already know basic TypeScript syntax, this guide will expand how you think about arrays as function parameters and give you practical patterns to apply immediately.
Background & Context
Array parameters sit at the intersection of data modeling and API design. Incorrect or ambiguous array typing causes bugs that are hard to find — for example, passing a mixed array where a homogeneous array is expected, or mutating inputs that should be treated as immutable. TypeScript's type system offers constructs to express intent: array types (T[] or Array
Understanding how TypeScript infers and checks array parameters helps you write safer APIs. It also affects performance and developer experience: accurate types yield better autocompletion and fewer runtime checks. This article builds on core TypeScript concepts and practical tooling guidance (for example, strict tsconfig flags recommended for robust typing). If you want to tighten compiler guarantees, see our guide on Recommended tsconfig.json strictness flags for new projects.
Key Takeaways
- Use simple array types like T[] for most homogeneous lists.
- Prefer readonly T[] when the function must not mutate the input.
- Use tuples for fixed-length, heterogeneous arrays to enforce positions.
- Use generics (function
(items: T[]) => ...) to write reusable, type-safe functions. - Rest parameters are syntactic sugar but different from explicit arrays for callers.
- Narrow unions and apply type guards for arrays with mixed element types.
- Combine mapped types and utility types to produce derived arrays with strong typing.
Prerequisites & Setup
You should be comfortable with basic TypeScript syntax: types, interfaces, generics, and function declarations. A modern TypeScript compiler (>= 4.x) is assumed. Set up a small project with:
- Node.js and npm/yarn
- typescript installed as a dev dependency:
npm install -D typescript - A tsconfig.json with strict flags enabled (recommended) — see our notes on tsconfig strictness flags.
Use an editor with TypeScript language support (VS Code is recommended) to see typing hints and quick fixes.
Main Tutorial Sections
1) Simple Homogeneous Array Parameters
For a function that sums numbers, the signature is straightforward:
function sum(numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
const total = sum([1, 2, 3]);Use T[] or Array<T> interchangeably. Prefer T[] for brevity and idiomatic TS. This pattern is the building block for more advanced techniques.
When working with higher-order operations like map/filter/reduce, consult our guide on Typing Array Methods in TypeScript: map, filter, reduce for patterns to keep types exact while composing list transforms.
2) Readonly Arrays: Preventing Accidental Mutation
If the function should not modify the input, annotate it as readonly:
function joinWords(words: readonly string[]): string {
// words.push('x') // error: Property 'push' does not exist
return words.join(' ');
}Using readonly T[] communicates intent and prevents accidental side effects. For a deeper discussion of when to use readonly vs. immutability libraries, see Using Readonly vs. Immutability Libraries in TypeScript.
3) Tuple Parameters for Fixed Shapes
Tuples enforce position and length. For a function that always expects a pair, use a tuple type:
function formatPoint(point: [number, number]): string {
const [x, y] = point;
return `(${x}, ${y})`;
}
formatPoint([10, 20]); // ok
formatPoint([10]); // errorTuples shine when the array serves as a fixed contract rather than a list. They also support labeled tuple elements (TS 4.x+) for additional clarity.
4) Generics: Writing Reusable Array Parameters
Generics let you write a function that accepts arrays of any element type while preserving that element type in results:
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]); // inferred as number | undefined
const s = first(['a', 'b']); // inferred as string | undefinedCombine generics with constraints when you need certain operations on elements:
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}Generics are essential for library code and APIs that operate on lists of differing element types. If you use callbacks with arrays, patterns from our Typing Callbacks in TypeScript guide will help you keep signatures tight.
5) Rest Parameters vs Array Parameters
Rest parameters are typed as arrays but change how callers pass values:
function pushAll<T>(target: T[], ...items: T[]) {
target.push(...items);
}
pushAll(myArray, a, b, c); // vs pushAll(myArray, [a, b, c])Choose rest when you want convenience for callers to pass multiple discrete arguments. Choose an explicit array parameter when the data naturally arrives as a collection (e.g., from an API or DB).
When designing APIs for UI or server frameworks, prefer explicit arrays for serialized payloads; in event handlers or builders, rest can improve ergonomics. See our guide on Typing Function Components in React — A Practical Guide for patterns around event handlers and props that may receive arrays.
6) Mixed Arrays, Unions, and Narrowing
Lists sometimes contain multiple possible types. A naive union Array<A | B> is valid but can be awkward to use. Use discriminated unions and type guards to work with mixed elements:
type Cat = { type: 'cat'; meow: () => void };
type Dog = { type: 'dog'; bark: () => void };
type Pet = Cat | Dog;
function handlePets(pets: Pet[]) {
for (const pet of pets) {
if (pet.type === 'cat') {
pet.meow();
} else {
pet.bark();
}
}
}Design your data structures with discriminators (type fields) when possible to make runtime checks trivial.
7) Typing Transformations: map, filter, reduce with Precise Types
When transforming arrays, keep types precise to avoid losing information. For example, filter narrows types but TypeScript needs explicit guards:
function isNumber(x: unknown): x is number {
return typeof x === 'number';
}
function onlyNumbers(xs: unknown[]): number[] {
return xs.filter(isNumber);
}For map/reduce, use generics to carry input and output types through:
function mapToLengths(xs: string[]): number[] {
return xs.map(s => s.length);
}If you need advanced helpers for array operations, our article on Typing Array Methods in TypeScript: map, filter, reduce dives into patterns for preserving types across chained calls.
8) Interoperability: Receiving Arrays from External Sources
When arrays come from requests, JSON, or databases, their runtime shape may be uncertain. Use runtime validation (e.g., zod, io-ts) or manual guards and then narrow types before calling typed functions:
// Example: Validate request body in Express
import type { Request, Response } from 'express';
function handleRequest(req: Request, res: Response) {
const maybeIds = req.body.ids;
if (!Array.isArray(maybeIds) || !maybeIds.every(id => typeof id === 'string')) {
res.status(400).send('Invalid payload');
return;
}
processIds(maybeIds); // typesafe
}For Express-specific patterns and properly typing request/response handlers, check our guide on Typing Basic Express.js Request and Response Handlers in TypeScript.
9) Arrays with Database Models: Mongoose and Typed Schemas
Arrays often appear in persistence models (tags, coordinates, related IDs). When working with Mongoose, ensure your TypeScript model types match schema expectations. Use typed interfaces for documents and arrays:
interface UserDoc {
name: string;
roles: string[]; // array parameter in many functions
}
function isAdmin(user: UserDoc) {
return user.roles.includes('admin');
}If you're typing Mongoose schemas and models, see Typing Mongoose Schemas and Models (Basic) for examples and pitfalls.
10) Combining Arrays and Objects: Keys, Values, Entries
Often you convert objects to arrays via Object.keys/values/entries. Typing these helpers correctly preserves better downstream types:
const obj = { a: 1, b: 2 } as const;
const entries = Object.entries(obj) as [keyof typeof obj, number][];When converting between objects and arrays, you may need assertions or helper functions to retain precise types. For more patterns, review Typing Object Methods (keys, values, entries) in TypeScript.
Advanced Techniques
Beyond the basics, apply advanced TypeScript features to write more expressive array parameter types. Use conditional mapped types to transform element shapes while keeping strong inference. For example, create a helper that maps an array of models to an array of partial views:
type View<T> = { id: T['id']; preview: Partial<T> };
function toViews<T extends { id: string }>(items: T[]): View<T>[] {
return items.map(item => ({ id: item.id, preview: {} }));
}You can also combine tuple operations with variadic tuple types (TS 4.0+) to type functions that accept varying fixed sections, such as a header tuple followed by repeated items. Performance-wise, keep heavy mapped types at the API boundary and avoid overcomplicated types deep inside hot paths, as complex types may slow down IDE responsiveness.
If you encounter type-level errors like "This expression is not callable" while manipulating arrays of functions, our troubleshooting guide on Fixing the "This expression is not callable" Error in TypeScript can help resolve confusing cases.
Best Practices & Common Pitfalls
Do:
- Prefer specific types over
any[]orunknown[]when you can describe element shapes. - Use
readonlyfor inputs that should not be mutated. - Prefer generics for library APIs to preserve element types.
- Use discriminated unions for heterogeneous arrays so narrowing is straightforward.
Don't:
- Rely on structural inference from implementation details; declare intent when necessary.
- Modify input arrays unless the API explicitly allows it — mutable side effects are a source of bugs.
- Overuse type assertions (
as) to silence the compiler; instead add precise guards or refine types.
Common pitfalls:
- Losing specificity after map/filter operations — use type predicates when filtering.
- Confusing rest parameters with array parameters — callers and usage patterns differ.
- Passing tuples where arrays are expected or vice versa — check call-sites.
For guidance on naming and organizing types when arrays are part of larger models, see Naming Conventions in TypeScript (Types, Interfaces, Variables).
Real-World Applications
-
API handlers: endpoints that receive arrays of resource IDs or objects — validate, narrow, then pass to typed services. See Typing Basic Express.js Request and Response Handlers in TypeScript for examples of validating request arrays.
-
React props: components that accept lists (e.g., items, rows) — specify
readonlywhen the component shouldn't mutate its input. See Typing Props and State in React Components with TypeScript and our guide on Typing Function Components in React — A Practical Guide for component patterns that accept arrays. -
Database models: arrays for relationships, tags, or embedded documents — ensure your schema and TypeScript interfaces match. See Typing Mongoose Schemas and Models (Basic) for patterns to align DB schemas with TS types.
-
Utilities and libraries: functions operating on arrays (dedupe, groupBy, chunk) should be generic and preserve element types so library consumers get correct inference.
Conclusion & Next Steps
Typing function parameters as arrays in TypeScript is straightforward for simple scenarios but becomes powerful when you apply readonly, tuples, generics, and utility types. Start by choosing the most specific type that communicates your intent, then refine with generics and guards as needed. Next, apply these patterns in your codebase and consider reading deeper on related topics: array method typing, callbacks, and strict compiler flags to maximize type safety.
Recommended next reads include our guides on Typing Array Methods in TypeScript: map, filter, reduce and Typing Callbacks in TypeScript: Patterns, Examples, and Best Practices.
Enhanced FAQ
Q1: When should I use T[] vs Array
A1: Semantically they are equivalent. Use T[] for brevity and consistency in most code. Array<T> is sometimes clearer when used with complex generics (e.g., ReadonlyArray<T> vs readonly T[]). Choose the style consistent with your codebase.
Q2: How do I type a function that accepts an array of tuples, e.g., coordinates?
A2: Use a tuple element type: function f(coords: [number, number][]) {}. Each element is a tuple of length two. If the tuple should be readonly, use readonly [number, number][] or readonly ([number, number])[] depending on intent.
Q3: Why does filter lose type information?
A3: Array.prototype.filter isn't typed to know how your predicate narrows the union unless you declare a type predicate. Example:
function isNumber(x: unknown): x is number { return typeof x === 'number'; }
const nums = [1, 'a', 2].filter(isNumber); // number[]Without a x is T predicate, the compiler conservatively keeps the union type.
Q4: Should I mark array parameters readonly by default?
A4: If your function does not mutate the input, prefer readonly to communicate intent and get compiler checks against accidental mutation. However, if mutation is an intended optimization (and clearly documented), accept a mutable parameter.
Q5: How do I type arrays that come from JSON or untyped sources?
A5: Validate at runtime (manual guards or a validation library like zod or io-ts) then narrow the type before passing to typed functions. Basic checks include Array.isArray(value) and value.every(...) for element types.
Q6: Can I use variadic tuple types with arrays?
A6: Yes — TypeScript supports variadic tuple types, which are useful for functions that accept a mix of fixed and variable parts. Example: function f<T extends any[]>(...args: [string, ...T]) {} types the first argument as a string and the rest as a tuple of arbitrary length.
Q7: How do generics interact with array inference?
A7: Generics preserve element types. When you call a generic function with an array literal, TypeScript infers the generic type parameter based on the array's elements. If inference fails or you want a more specific type, provide the type parameter explicitly: first<number>([1,2,3]).
Q8: How do I avoid performance issues with very deep mapped types on arrays?
A8: Keep complex type-level computations at API boundaries and avoid cascading mapped types in tight loops. If editor performance slows, simplify types or split complex types into named interfaces and incremental types.
Q9: How should I document functions that accept arrays of mixed types?
A9: Use discriminated unions in the type definitions and add a short comment showing examples of valid shapes. This makes runtime checks and compiler narrowing straightforward.
Q10: Any tips for integrating array typing in React and Express apps?
A10: For React, prefer readonly arrays for props that represent data (not mutable state). Check our Typing Props and State in React Components with TypeScript and Typing Event Handlers in React with TypeScript for patterns. For Express, validate request array bodies and then pass the narrowed arrays to handlers; see Typing Basic Express.js Request and Response Handlers in TypeScript for typical patterns.
If you want hands-on exercises, try converting a small JavaScript utility that manipulates arrays to TypeScript using the patterns above, and enable strict mode in your tsconfig to see where typing clarifications are required. For additional reference on asynchronous array operations (like awaiting Promise arrays), see our guide on Typing Asynchronous JavaScript: Promises and Async/Await.
