Zip multiple arrays together
typescriptA type-safe TypeScript function that zips multiple arrays together into tuples, similar to Python's zip(). Handles arrays of different lengths by stopping at the shortest one.
Code
type ZipResult<T extends unknown[][]> = {
[K in keyof T]: T[K] extends (infer U)[] ? U : never;
}[];
function zip<T extends unknown[][]>(...arrays: T): ZipResult<T> {
if (arrays.length === 0) {
return [] as ZipResult<T>;
}
const minLength = Math.min(...arrays.map(arr => arr.length));
const result: unknown[][] = [];
for (let i = 0; i < minLength; i++) {
const tuple = arrays.map(arr => arr[i]);
result.push(tuple);
}
return result as ZipResult<T>;
}
// Type-safe overloads for common use cases (2-5 arrays)
function zipTyped<A, B>(a: A[], b: B[]): [A, B][];
function zipTyped<A, B, C>(a: A[], b: B[], c: C[]): [A, B, C][];
function zipTyped<A, B, C, D>(a: A[], b: B[], c: C[], d: D[]): [A, B, C, D][];
function zipTyped<A, B, C, D, E>(a: A[], b: B[], c: C[], d: D[], e: E[]): [A, B, C, D, E][];
function zipTyped(...arrays: unknown[][]): unknown[][] {
if (arrays.length === 0) return [];
const minLength = Math.min(...arrays.map(arr => arr.length));
return Array.from({ length: minLength }, (_, i) =>
arrays.map(arr => arr[i])
);
}
// Usage examples
const numbers = [1, 2, 3, 4];
const letters = ['a', 'b', 'c'];
const booleans = [true, false, true, false, true];
// Using the generic zip function
const zipped = zip(numbers, letters);
console.log('Zipped (numbers, letters):', zipped);
// Output: [[1, 'a'], [2, 'b'], [3, 'c']]
// Using the type-safe overloaded version
const zippedTyped = zipTyped(numbers, letters, booleans);
console.log('Zipped (numbers, letters, booleans):', zippedTyped);
// Output: [[1, 'a', true], [2, 'b', false], [3, 'c', true]]
// Iterating over zipped results
for (const [num, letter] of zipTyped(numbers, letters)) {
console.log(`Number: ${num}, Letter: ${letter}`);
}
// Practical example: Creating objects from parallel arrays
const names = ['Alice', 'Bob', 'Charlie'];
const ages = [25, 30, 35];
const cities = ['NYC', 'LA', 'Chicago'];
const people = zipTyped(names, ages, cities).map(([name, age, city]) => ({
name,
age,
city
}));
console.log('People:', people);
// Output: [{name: 'Alice', age: 25, city: 'NYC'}, ...]How It Works
The zip function combines multiple arrays element-by-element, producing an array of tuples where each tuple contains corresponding elements from each input array. This pattern is commonly found in languages like Python and is incredibly useful for parallel iteration over multiple data sources.
The implementation provides two approaches: a generic zip function using mapped types, and a zipTyped function with explicit overloads. The generic version uses ZipResult<T> to infer the return type automatically, which works well but can sometimes produce complex type signatures. The overloaded version provides cleaner type inference for the common cases of 2-5 arrays, making IDE autocomplete and error messages more helpful.
The core algorithm finds the minimum length among all input arrays using Math.min(...arrays.map(arr => arr.length)), ensuring we stop at the shortest array and avoid undefined values. This is the standard behavior for zip operations across most programming languages. The iteration then builds tuples by collecting the element at each index from every input array.
Edge cases are handled gracefully: empty input returns an empty array, a single array returns an array of single-element tuples, and arrays of vastly different lengths simply stop at the shortest. One important consideration is that this implementation creates new arrays for the result, so for very large datasets or performance-critical code, you might consider a generator-based approach that yields tuples lazily.
Use this pattern when you need to iterate over multiple related arrays in parallel, transform parallel data structures into objects, or implement algorithms that require element-wise operations across collections. Avoid it when you need to preserve elements from longer arrays (consider zipLongest instead) or when working with truly massive datasets where memory allocation becomes a concern.