How to Map an Array of Objects to Another Array in TypeScript

You map an array of objects to another array in TypeScript with Array.prototype.map(). Give the callback a typed input and an explicit return type, and wrap the object literal in parentheses:

const cards: EmployeeCard[] = employees.map((e: Employee): EmployeeCard => ({
  id: e.id,
  name: `${e.firstName} ${e.lastName}`,
}));

I compiled every example below in strict mode with TypeScript 7.0.2 and Node.js v22.22.2, and each output block is copied from my console.

Map an array of objects to a new interface

Here, each Employee in Austin or Denver becomes a lighter EmployeeCard. If you’re new to typed object arrays, start with arrays of objects in TypeScript.

interface Employee {
  id: number;
  firstName: string;
  lastName: string;
  city: string;
  salary: number;
}

const employees: Employee[] = [
  { id: 101, firstName: "Emma", lastName: "Carter", city: "Austin", salary: 98500 },
  { id: 102, firstName: "Liam", lastName: "Brooks", city: "Denver", salary: 87250 },
  { id: 103, firstName: "Olivia", lastName: "Hayes", city: "Austin", salary: 112000 },
];

interface EmployeeCard {
  id: number;
  name: string;
}

const cards: EmployeeCard[] = employees.map((e: Employee): EmployeeCard => ({
  id: e.id,
  name: `${e.firstName} ${e.lastName}`,
}));

console.log(cards);

Output:

[
  { id: 101, name: 'Emma Carter' },
  { id: 102, name: 'Liam Brooks' },
  { id: 103, name: 'Olivia Hayes' }
]
Command Prompt output of TypeScript map() converting Employee objects into EmployeeCard objects
map() returns a new array with the same length; the original employees keep every field.

The explicit : EmployeeCard return type matters. Without it, a typo only shows up later, wherever the array gets used.

Why does map() return undefined for my objects?

In an arrow function, braces start a block, not an object. So { id: e.id } is a label plus an expression, and you get nothing back:

// Employee interface and employees array from the first example

// Braces without parentheses = a function body with a label, not an object
const broken = employees.map((e) => { id: e.id });
const fixed = employees.map((e) => ({ id: e.id }));

console.log("broken:", broken);
console.log("fixed: ", fixed);

Output:

broken: [ undefined, undefined, undefined ]
fixed:  [ { id: 101 }, { id: 102 }, { id: 103 } ]

Parentheses make it an expression again. For loops that don’t need a return value, the forEach() method is the better fit.

TypeScript map() type errors caught by tsc

Here’s where TypeScript earns its keep. The first callback forgets city; the second leaks salary into a type that doesn’t allow it:

interface Employee { id: number; firstName: string; city: string; salary: number }
interface EmployeeCard { id: number; name: string; city: string }

const employees: Employee[] = [{ id: 101, firstName: "Emma", city: "Austin", salary: 98500 }];

const missing = employees.map((e): EmployeeCard => ({ id: e.id, name: e.firstName }));

const extra = employees.map((e): EmployeeCard => ({
  id: e.id,
  name: e.firstName,
  city: e.city,
  salary: e.salary,
}));

Output:

map_errors.ts(6,53): error TS2741: Property 'city' is missing in type '{ id: number; name: string; }' but required in type 'EmployeeCard'.
map_errors.ts(12,3): error TS2353: Object literal may only specify known properties, and 'salary' does not exist in type 'EmployeeCard'.
Command Prompt output of tsc errors TS2741 property city is missing and TS2353 object literal may only specify known properties
TS2741 flags the missing city; TS2353 flags the extra salary property.

Excess-property checks only apply to fresh object literals, which is what a map() callback returns. Interfaces for object arrays are covered in TypeScript interface for an array of objects.

Add, rename or omit object fields in map()

Destructuring in the callback keeps the mapping short. Rest spread drops a field, and renaming happens inline:

// Employee interface and employees array from the first example

type PublicEmployee = Omit<Employee, "salary">;
type Directory = Pick<Employee, "id" | "city"> & { fullName: string };

// Omit a field with rest spread
const publicList: PublicEmployee[] = employees.map(({ salary, ...rest }) => rest);

// Rename and combine fields while destructuring
const directory: Directory[] = employees.map(({ id, firstName: first, lastName: last, city }) => ({
  id,
  city,
  fullName: `${last}, ${first}`,
}));

console.log(publicList[0]);
console.log(directory[1]);

Output:

{ id: 101, firstName: 'Emma', lastName: 'Carter', city: 'Austin' }
{ id: 102, city: 'Denver', fullName: 'Brooks, Liam' }

Omit and Pick save you retyping fields. To add a field, I spread: ({ ...e, bonusUSD: (e.salary * 0.05).toFixed(2) }).

Spread copies are shallow, so nested objects are still shared. The TypeScript spread operator guide shows where that bites.

Use the index while mapping objects

The callback also receives the index and the whole array. Here I number US orders and format totals as dollars:

interface Order { orderId: string; total: number }

const orders: Order[] = [{ orderId: "A-5001", total: 249.5 }, { orderId: "A-5002", total: 1180 }];
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });

console.log(orders.map((o, i) => ({ line: i + 1, ...o, total: usd.format(o.total) })));

Output:

[
  { line: 1, orderId: 'A-5001', total: '$249.50' },
  { line: 2, orderId: 'A-5002', total: '$1,180.00' }
]

If you need one grand total rather than a new array, reach for the reduce() method in TypeScript.

Combine map with flatMap and filter

When one object should produce several rows, you want flatMap(). It maps, then flattens one level:

interface LineItem { sku: string; qty: number; price: number }
interface Order { orderId: string; status: "paid" | "refunded"; items: LineItem[] }

const orders: Order[] = [
  { orderId: "A-5001", status: "paid", items: [{ sku: "MUG-12", qty: 2, price: 14.99 }] },
  { orderId: "A-5002", status: "refunded", items: [{ sku: "BOOK-TS", qty: 1, price: 39.99 }] },
  { orderId: "A-5003", status: "paid", items: [{ sku: "MAT-YOGA", qty: 3, price: 25 }] },
];

// One output row per line item
const lines = orders.flatMap((o) =>
  o.items.map((it) => ({ orderId: o.orderId, sku: it.sku, amount: (it.qty * it.price).toFixed(2) }))
);
console.log(lines);

// Filter first, then map: only paid orders
const paidTotals = orders
  .filter((o) => o.status === "paid")
  .map((o) => ({ orderId: o.orderId, total: o.items.reduce((s, it) => s + it.qty * it.price, 0).toFixed(2) }));
console.log(paidTotals);

Output:

[
  { orderId: 'A-5001', sku: 'MUG-12', amount: '29.98' },
  { orderId: 'A-5002', sku: 'BOOK-TS', amount: '39.99' },
  { orderId: 'A-5003', sku: 'MAT-YOGA', amount: '75.00' }
]
[
  { orderId: 'A-5001', total: '29.98' },
  { orderId: 'A-5003', total: '75.00' }
]
Command Prompt output of TypeScript flatMap turning orders into line items, plus filter then map for paid orders
flatMap() flattens the line items, and filter().map() keeps only paid orders.

Filtering first keeps your map callback simple. For more patterns, see filtering an array of objects in TypeScript and flattening an array of arrays.

Map an array of objects with async functions

An async callback makes map() return promises. Await them with Promise.all() and you get results in the original order:

interface Store { id: number; city: string }
interface StoreSales { city: string; salesUSD: string }

const stores: Store[] = [
  { id: 1, city: "Austin" },
  { id: 2, city: "Denver" },
  { id: 3, city: "Chicago" },
];

const fetchSales = (id: number): Promise<number> =>
  new Promise((resolve) => setTimeout(() => resolve(id * 1520.4), 100 * (4 - id)));

async function main(): Promise<void> {
  const pending = stores.map(async (s) => ({ city: s.city, salesUSD: (await fetchSales(s.id)).toFixed(2) }));
  console.log("without await:", pending[0] instanceof Promise);

  const report: StoreSales[] = await Promise.all(pending);
  console.log(report);
}

main();

Output:

without await: true
[
  { city: 'Austin', salesUSD: '1520.40' },
  { city: 'Denver', salesUSD: '3040.80' },
  { city: 'Chicago', salesUSD: '4561.20' }
]
Command Prompt output of TypeScript map with an async callback and Promise.all returning sales per city in USD
Chicago resolves first, yet Promise.all() keeps Austin, Denver, Chicago in order.

You’ll find these array tutorials useful next:

Frequently asked questions

How do I map an array of objects to another array in TypeScript?

Call map() with a typed callback: (e: Employee): EmployeeCard => ({ ... }).

Does map() change the original array?

No. It returns a new array of the same length, as long as your callback returns new objects instead of mutating them.

How do I remove a property while mapping?

Use rest spread, ({ salary, ...rest }) => rest, typed as Omit<Employee, "salary">.

How do I use await inside map()?

Make the callback async and wrap the call in await Promise.all(items.map(async (x) => ...)).

Where is map() documented?

See Array.prototype.map() on MDN.