How to Sort by Enum in TypeScript (Numeric and String Enums)

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
Command Prompt output of TypeScript tickets sorted by a numeric Priority enum ascending and descending
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'
]
Command Prompt output of TypeScript orders sorted by a string enum alphabetically and with a Record order map
Alphabetical puts CANCELLED first; the order map puts orders that need work at the top.

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.
Command Prompt output of tsc errors for a Record missing an enum member and arithmetic on string enum values
The 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
Command Prompt output of TypeScript orders sorted by enum status, then placed date, then USD total
Ties on status fall through to the date, then the total, then the ID.

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