To sort by enum in TypeScript, compare the enum members inside sort(). A numeric enum is already a number, so a.priority - b.priority works. A string enum needs a rank map typed as Record<Enum, number>:
const STATUS_ORDER: Record<OrderStatus, number> = {
[OrderStatus.Processing]: 0,
[OrderStatus.Pending]: 1,
[OrderStatus.Shipped]: 2,
[OrderStatus.Delivered]: 3,
[OrderStatus.Cancelled]: 4,
};
orders.sort((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]);
Every snippet here ran in strict mode on TypeScript 7.0.2 and Node.js v22.22.2. The output blocks are pasted straight from my console, nothing retyped.
Sort an array of objects by a numeric enum
Numeric enum members compile to plain numbers, so the value you assign is the sort order. Here’s a help desk queue with five support tickets. If sort() comparators are new to you, sorting an array of objects by property value covers the basics.
enum Priority {
Low, // 0
Medium, // 1
High, // 2
Critical, // 3
}
interface Ticket {
id: number;
customer: string;
city: string;
priority: Priority;
}
const tickets: Ticket[] = [
{ id: 4101, customer: "Maria Lopez", city: "Phoenix", priority: Priority.Medium },
{ id: 4102, customer: "James Carter", city: "Boston", priority: Priority.Critical },
{ id: 4103, customer: "Ava Nguyen", city: "Seattle", priority: Priority.Low },
{ id: 4104, customer: "Noah Bennett", city: "Atlanta", priority: Priority.High },
{ id: 4105, customer: "Emma Brooks", city: "Denver", priority: Priority.Critical },
];
const show = (label: string, list: Ticket[]): void => {
console.log(label);
for (const t of list) {
console.log(` #${t.id} ${Priority[t.priority].padEnd(8)} ${t.customer}, ${t.city}`);
}
};
// Numeric enum members are numbers, so subtraction works
const ascending = [...tickets].sort((a, b) => a.priority - b.priority);
const descending = tickets.toSorted((a, b) => b.priority - a.priority);
show("Low -> Critical:", ascending);
show("Critical -> Low:", descending);
Output:
Low -> Critical:
#4103 Low Ava Nguyen, Seattle
#4101 Medium Maria Lopez, Phoenix
#4104 High Noah Bennett, Atlanta
#4102 Critical James Carter, Boston
#4105 Critical Emma Brooks, Denver
Critical -> Low:
#4102 Critical James Carter, Boston
#4105 Critical Emma Brooks, Denver
#4104 High Noah Bennett, Atlanta
#4101 Medium Maria Lopez, Phoenix
#4103 Low Ava Nguyen, Seattle

a - b gives Low to Critical; b - a flips it to Critical first.Two details matter. sort() changes the array in place, so I copy with [...tickets] or call toSorted(), which returns a new array. And Priority[t.priority] uses enum reverse mapping to print the name.
For descending order, just swap the operands. The same trick appears in sorting a number array in descending order.
Sort by a string enum with an explicit order map
String enums can’t be subtracted. You could call localeCompare(), but that sorts the values alphabetically, and alphabetical order almost never matches your workflow. Compare both on a list of US orders:
enum OrderStatus {
Pending = "PENDING",
Processing = "PROCESSING",
Shipped = "SHIPPED",
Delivered = "DELIVERED",
Cancelled = "CANCELLED",
}
interface Order {
id: string;
city: string;
status: OrderStatus;
totalUSD: number;
placed: Date;
}
const orders: Order[] = [
{ id: "A-5001", city: "Austin", status: OrderStatus.Shipped, totalUSD: 249.5, placed: new Date("2026-10-02T15:20:00Z") },
{ id: "A-5002", city: "Chicago", status: OrderStatus.Pending, totalUSD: 89.99, placed: new Date("2026-10-05T18:45:00Z") },
{ id: "A-5003", city: "Miami", status: OrderStatus.Cancelled, totalUSD: 1180, placed: new Date("2026-10-01T13:05:00Z") },
{ id: "A-5004", city: "Denver", status: OrderStatus.Processing, totalUSD: 432.1, placed: new Date("2026-10-04T16:30:00Z") },
{ id: "A-5005", city: "Boston", status: OrderStatus.Pending, totalUSD: 315, placed: new Date("2026-10-03T14:10:00Z") },
{ id: "A-5006", city: "Seattle", status: OrderStatus.Delivered, totalUSD: 64.25, placed: new Date("2026-09-28T20:00:00Z") },
{ id: "A-5007", city: "Atlanta", status: OrderStatus.Processing, totalUSD: 432.1, placed: new Date("2026-10-04T16:30:00Z") },
];
const list = (rows: Order[]): string[] => rows.map((o) => `${o.id} ${o.status}`);
// Fulfillment order: work in progress first, closed orders last
const STATUS_ORDER: Record<OrderStatus, number> = {
[OrderStatus.Processing]: 0,
[OrderStatus.Pending]: 1,
[OrderStatus.Shipped]: 2,
[OrderStatus.Delivered]: 3,
[OrderStatus.Cancelled]: 4,
};
// localeCompare sorts the enum VALUES alphabetically
const alphabetical = orders.toSorted((a, b) => a.status.localeCompare(b.status));
// The order map sorts by business meaning
const byWorkflow = orders.toSorted((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]);
console.log("alphabetical:", list(alphabetical));
console.log("by workflow: ", list(byWorkflow));
Output:
alphabetical: [
'A-5003 CANCELLED',
'A-5006 DELIVERED',
'A-5002 PENDING',
'A-5005 PENDING',
'A-5004 PROCESSING',
'A-5007 PROCESSING',
'A-5001 SHIPPED'
]
by workflow: [
'A-5004 PROCESSING',
'A-5007 PROCESSING',
'A-5002 PENDING',
'A-5005 PENDING',
'A-5001 SHIPPED',
'A-5006 DELIVERED',
'A-5003 CANCELLED'
]

I like Record<OrderStatus, number> because the compiler checks it. Every enum member must have a rank, and nothing else can sneak in.
You can also change the display order later without touching the enum itself. Wondering whether an enum is the right tool at all? See enums vs string literal unions.
What happens if the order map misses an enum member?
Say someone adds a status later and forgets the map, or tries to subtract string enum members. Here’s what tsc says about both:
enum OrderStatus {
Pending = "PENDING",
Processing = "PROCESSING",
Shipped = "SHIPPED",
Delivered = "DELIVERED",
Cancelled = "CANCELLED",
}
interface Order { id: string; status: OrderStatus }
const orders: Order[] = [];
// 1) Someone forgot Cancelled
const STATUS_ORDER: Record<OrderStatus, number> = {
[OrderStatus.Processing]: 0,
[OrderStatus.Pending]: 1,
[OrderStatus.Shipped]: 2,
[OrderStatus.Delivered]: 3,
};
// 2) Subtracting string enum members
orders.sort((a, b) => a.status - b.status);
Output:
sort_enum_errors.ts(13,7): error TS2741: Property 'CANCELLED' is missing in type '{ PROCESSING: number; PENDING: number; SHIPPED: number; DELIVERED: number; }' but required in type 'Record<OrderStatus, number>'.
sort_enum_errors.ts(21,23): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
sort_enum_errors.ts(21,34): error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

Record type catches the missing Cancelled rank; subtraction on string enums doesn’t compile.That first error is why I prefer a Record over a plain object or an array of strings. With a numeric enum, the subtraction compiles fine. If you need the number behind a member, converting an enum to a number explains the options.
Sort by enum declaration order with Object.values()
Sometimes the declaration order already is the order you want. For a string enum, Object.values() returns the values in that order, and indexOf() turns each one into a rank:
// OrderStatus enum, Order interface, orders and list() from the string enum example
// String enum: Object.values() returns the values in declaration order
const DECLARED = Object.values(OrderStatus);
console.log(DECLARED);
const byDeclaration = orders.toSorted(
(a, b) => DECLARED.indexOf(a.status) - DECLARED.indexOf(b.status)
);
console.log(list(byDeclaration));
// Numeric enum: the object also holds the reverse mapping
enum Priority { Low, Medium, High, Critical }
console.log(Object.values(Priority));
console.log(Object.values(Priority).filter((v) => typeof v === "number"));
Output:
[ 'PENDING', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CANCELLED' ]
[
'A-5002 PENDING',
'A-5005 PENDING',
'A-5004 PROCESSING',
'A-5007 PROCESSING',
'A-5001 SHIPPED',
'A-5006 DELIVERED',
'A-5003 CANCELLED'
]
[ 'Low', 'Medium', 'High', 'Critical', 0, 1, 2, 3 ]
[ 0, 1, 2, 3 ]
Watch out with numeric enums. The compiled object holds both directions, so Object.values() returns the names too. Filter on typeof v === "number" to keep only the values.
I use this for short enums only. indexOf() scans the array on every comparison, so for big lists build a Map once. You can learn more ways to read members in getting all enum values in TypeScript.
Multi-key sort: enum first, then date and amount
Real queues rarely sort on one field. This warehouse wants status, then the oldest order, then the bigger total, then the ID:
// OrderStatus enum, Order interface, orders and list() from the string enum example
// STATUS_ORDER map from the string enum example
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
const when = (d: Date): string =>
d.toLocaleString("en-US", { timeZone: "America/Chicago", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
// 1) status (workflow order) 2) oldest first 3) bigger total first 4) id as a final tie-breaker
const queue = orders.toSorted(
(a, b) =>
STATUS_ORDER[a.status] - STATUS_ORDER[b.status] ||
a.placed.getTime() - b.placed.getTime() ||
b.totalUSD - a.totalUSD ||
a.id.localeCompare(b.id)
);
for (const o of queue) {
console.log(`${o.status.padEnd(10)} ${when(o.placed).padEnd(16)} ${usd.format(o.totalUSD).padStart(9)} ${o.id} ${o.city}`);
}
Output:
PROCESSING Oct 4, 11:30 AM $432.10 A-5004 Denver
PROCESSING Oct 4, 11:30 AM $432.10 A-5007 Atlanta
PENDING Oct 3, 9:10 AM $315.00 A-5005 Boston
PENDING Oct 5, 1:45 PM $89.99 A-5002 Chicago
SHIPPED Oct 2, 10:20 AM $249.50 A-5001 Austin
DELIVERED Sep 28, 3:00 PM $64.25 A-5006 Seattle
CANCELLED Oct 1, 8:05 AM $1,180.00 A-5003 Miami

The trick is ||. A comparison that returns 0 is falsy, so JavaScript moves on to the next key. Orders A-5004 and A-5007 tie on status, time and $432.10, so the ID decides.
Dates compare with getTime(), which is time-zone safe; Central Time is only for display. For more date patterns, read sorting arrays by date in TypeScript.
How do you handle unknown enum values when sorting?
Data from an API or a database isn’t checked by the compiler. If a status like "RETURNED" shows up, the map lookup returns undefined, and the comparator returns NaN:
enum OrderStatus {
Pending = "PENDING",
Processing = "PROCESSING",
Shipped = "SHIPPED",
Delivered = "DELIVERED",
Cancelled = "CANCELLED",
}
// STATUS_ORDER map from the string enum example
// Data from an API: status is just a string, and "RETURNED" isn't in the enum
const rows: { id: string; status: string }[] = JSON.parse(`[
{"id":"B-1","status":"DELIVERED"}, {"id":"B-2","status":"RETURNED"},
{"id":"B-3","status":"SHIPPED"}, {"id":"B-4","status":"PROCESSING"},
{"id":"B-5","status":"PENDING"}
]`);
const ids = (r: { id: string; status: string }[]): string => r.map((x) => `${x.id}:${x.status}`).join(" ");
// Naive: the cast hides the problem, undefined - number is NaN
const naive = rows.toSorted(
(a, b) => STATUS_ORDER[a.status as OrderStatus] - STATUS_ORDER[b.status as OrderStatus]
);
console.log("naive:", ids(naive));
console.log("Infinity - Infinity =", Infinity - Infinity);
// Safe: a type guard, and unknown val