Editor’s note: This article was last reviewed and updated by Ibiyemi Adekwakun in January 2025 to include new sections on using the for..in
loop with numeric enums, the for..in
loop with string enums, reverse mapping in numeric enums, and converting enums to typed arrays.
Enums are ubiquitous in modern programming. They are normally widely used to model categories of things, such as the possible states of a traffic light, the days of the week, and the months of the year.
One of the advantages of enums is that they let us map short lists of values into numbers, making it easier to compare them and work with them in general. Enums have evolved a lot over the years, from simple mapping from “words” to numbers to complex data structures similar to classes, with methods and parameters.
In this article, we’ll explore different approaches for iterating over enums in TypeScript.
TypeScript enums are simple objects:
enum TrafficLight { Green = 1, Yellow, Red }
In the definition above, Green
is mapped to the number 1
. The subsequent members are mapped to auto-incremented integers. Hence, Yellow
is mapped to 2
, and Red
to 3
.
If we didn’t specify the mapping Green = 1
, TypeScript would pick 0
as a starting index.
However, sometimes we want to iterate over a TypeScript enum. This is particularly useful when, for example, we want to perform some actions for each element of an enumeration.
In TypeScript, there are a few ways to iterate over enums. We’ll explore them in the following sections.
The simplest way to iterate over an enum in TypeScript is to convert it to an array using the inbuilt Object.keys()
and Object.values()
methods. The former returns an array containing the keys of the enum object, and the latter returns an array of the enum’s values.
The following code snippet shows how to use the inbuilt object method to list the keys and values of an enum:
const keys = Object.keys(TrafficLight) keys.forEach((key, index) => { console.log(`${key} has index ${index}`) })
The example above prints the following:
"1 has index 0" "2 has index 1" "3 has index 2" "Green has index 3" "Yellow has index 4" "Red has index 5"
The first three lines may look confusing — why do we have three unexpected keys? This is because enums are compiled in two ways:
0
(or with the number we specify, 1
, in our example). This is represented by the first three lines in the output abovestring
value is assigned a numeric key; this is a reverse mappingIf we want to only list the string keys, we’ll have to filter out the numeric ones:
const stringKeys = Object .keys(TrafficLight) .filter((v) => isNaN(Number(v))) stringKeys.forEach((key, index) => { console.log(`${key} has index ${index}`) })
In this case, the snippet above prints the following:
"Green has index 0" "Yellow has index 1" "Red has index 2"
From the output above, we can see that the index
parameter has nothing to do with the actual numeric value in the enum. In fact, it is just the index of the key in the array returned by Object.keys()
.
Similarly, we can iterate over the enum values:
const values = Object.values(TrafficLight) values.forEach((value) => { console.log(value) })
Again, the snippet above prints both string and numeric values:
"Green" "Yellow" "Red" 1 2 3
Let’s say we’re interested in the numeric values, not in the string ones. We can filter the latter out similar to before, using .filter((v) => !isNaN(Number(v)))
.
It’s worth noting that we have to filter the values only because we’re dealing with numeric enums. If we had assigned a string value to the members of our enumeration, we wouldn’t have to filter out numeric keys and values:
enum TrafficLight { Green = "G", Yellow = "Y", Red = "R" } Object.keys(TrafficLight).forEach((key, index) => { console.log(`${key} has index ${index}`) }) Object.values(TrafficLight).forEach((value) => { console.log(value) })
The snippet above prints what follows, where the first three lines are from the first forEach
loop and the last three lines are from the second forEach
loop:
"Green has index 0" "Yellow has index 1" "Red has index 2" "G" "Y" "R"
String enums are very useful, as they are more human-readable than numeric ones. We can also mix numeric and string enums, although it is not advisable to do so.
Using the Object.keys()
and Object.values()
methods to iterate over the members of enums is a simple solution. Nonetheless, it is not very type-safe, as TypeScript returns keys and values as strings or numbers, thus not preserving the enum typing.
Before we explore other options for iterating over enums, let’s talk about reverse mapping in TypeScript’s numeric enums.
Reverse mapping is a TypeScript feature that compiles numeric enums to objects with both a name → value
property assignment and a value → name
property assignment.
See the following numeric enum:
enum Drinks { WATER = 1, SODA, JUICE }
This enum would be compiled in TypeScript to an object with a form similar to the following:
const drinks = { '1': 'WATER', '2': 'SODA', '3': 'JUICE', 'WATER': '1', 'SODA': '2', 'JUICE': '3' }
This is useful for providing human-readable references to enum values and making debugging easier.
It’s also important to note that TypeScript only provides this functionality to numeric enums.
for
loopsInstead of relying on Object.keys()
and Object.values()
, another approach is to use for
loops to iterate over the keys and then use reverse mapping to get the enum values.
TypeScript offers three different kinds of for loop statements, including the for..in
and for..of
loops, which can be used to iterate an enum.
The for..in
statement loops through the keys of the enum; this works on both numeric and string enums. As mentioned earlier, numerical enums return both the defined keys and the assigned numerical value from reverse mapping so iterating over the defined keys alone will require some additional logic. With string enums, it will only loop through the defined keys. You can think of using for..in
as a loop of what Object.keys
returns.
On the other hand, the for..of
statement cannot be run directly on an enum like we run for..in
— it requires some additional logic to iterate an enum.
Let’s look at a few examples.
For..in
loop through numeric enumsenum TrafficLight { Green, Yellow, Red } for (const tl in TrafficLight) { const value = TrafficLight[tl] if (typeof value === "string") { console.log(`Value: ${TrafficLight[tl]}`) } }
The script above will print the following:
"Value: Green" "Value: Yellow" "Value: Red"
Notice that, in the example above, we filtered out the numeric values. This way, we can extract the member names of our enum. If we wanted to fetch them, instead of the string values, we could use a different guard in the if
statement: typeof value !==
"string"
.
For..in
loop through string enumsenum TrafficLight { Green = "G", Yellow = "Y", Red = "R" } for (const tl in TrafficLight) { console.log(`Value: ${TrafficLight[tl]}`) }
The script above will print the following:
Value: G Value: Y Value: R
With this example, we didn’t need the typeof value === "string"
condition check because string enums don’t have reverse mapping and won’t return any numerical keys. Our example logs our enum’s assigned values but if we wanted the keys, we could log the iterator tl
instead.
For..of
loop for enumsThe For..of
loop statement can be used to loop through a JavaScript iterable object. If we tried to use a for..of
loop directly on an enum as we did for..in
, we would get an error:
enum TrafficLight { Green = "G", Yellow = "Y", Red = "R" } for (const tl of TrafficLight) { console.log(`Value: ${tl}`) }
If we tried to run the above block, it would throw Type 'typeof TrafficLight' is not an array type or a string type.
This is because enums are not iterable.
To fix this, we have to provide the for..of
statement an iterable to loop through. An option would be to extract the keys of our enum using Object.keys()
for the for..of
loop to use:
enum TrafficLight { Green = "G", Yellow = "Y", Red = "R" } for (const tl of Object.keys(TrafficLight)) { console.log(`Value: ${TrafficLight[tl]}`) }
The script above will print:
Value: G Value: Y Value: R
Earlier, we talked about numeric enums being reverse-mapped. So while our above snippet works as expected for string enums, it will require a little tweaking for numeric enums as it did with the for..in
loops:
enum TrafficLight { Green = 1, Yellow, Red } for (const tl of Object.keys(TrafficLight)) { const value = TrafficLight[tl] if (typeof value === "string") { console.log(`Value: ${TrafficLight[tl]}`) } }
It will give us the following:
Value: Green Value: Yellow Value: Red
You could also choose to use the
for..of
loop on the values of the enum instead.
One of the benefits of defining an enum is that we provide an object with a set of limited constants.
We have explored several options to iterate an enum by converting it to an iterable object using Object.keys()
or Object.values
; however, with these options, we lose the strict typing of our enum to the specified keys alone because Object.keys()
returns an array of strings.
To fix this, we’ll have to explicitly inform TypeScript about the type of our enum’s keys:
enum TrafficLight { Green = "G", Yellow = "Y", Red = "R" } function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] { return Object.keys(obj).filter(k => Number.isNaN(k)) as K[] }
In the above code, the enumKeys
function simply extracts the keys of the enum and returns them as an array. With this, the return type of enumKeys(TrafficLight)
is ("Green" | "Yellow" | "Red")[]
.
The filter method
.filter(k => Number.isNaN(k))
is used to ensure support for numeric enums by extracting the numeric keys from reverse mapping.
This typed array can then be looped through using a for..of
loop:
for (const tl of enumKeys(TrafficLight)) { const value = TrafficLight[tl] if (typeof value === "string") { console.log(`Value: ${TrafficLight[tl]}`) } }
We use
for..of
rather thanfor…in
in our for loop because the latter returns an index of items in our typed array.
Lodash is a JavaScript library that provides many utility methods for common programming tasks. Such methods use the functional programming paradigm to let us write more concise and readable code.
To install Lodash into our project, we can run the following command:
npm install lodash --save
The npm install lodash
command will install the module, and the save
flag will update the contents of the package.json
file.
It turns out we can leverage its forIn
method to iterate over an enum in TypeScript:
import { forIn } from 'lodash' enum TrafficLight { Green = 1, Yellow, Red, } forIn(TrafficLight, (value, key) => console.log(key, value))
The forIn
method iterates over both keys and values of a given object, invoking, in its simplest form, a given function for each (key, value)
pair. It is essentially a combination of the Object.keys()
and Object.values()
methods.
As we might expect, if we run the example above, we’ll get both string and numeric keys:
1 Green 2 Yellow 3 Red Green 1 Yellow 2 Red 3
As before, we can easily filter out string or numeric keys, depending on our needs:
import { forIn } from 'lodash' enum TrafficLight { Green = 1, Yellow, Red, } forIn(TrafficLight, (value, key) => { if (isNaN(Number(key))) { console.log(key, value) } })
The example above prints the following:
Green 1 Yellow 2 Red 3
In this case, the type of key
is string
, whereas the type of value
is TrafficLight
. Hence, this solution preserves the typing of the value
:
import { forIn } from 'lodash' enum TrafficLight { Green = "G", Yellow = "Y", Red = "R" } forIn(TrafficLight, (value, key) => { if (isNaN(Number(key))) { console.log(key, value) } })
As we might expect, the example above prints the following:
Green G Yellow Y Red R
In this article, we explored multiple techniques to iterate through enums in TypeScript, including built-in object methods, for
loops, and third-party libraries like Lodash. By leveraging methods such as Object.keys()
and Object.values()
, we learned how to handle both string and numeric enums. Additionally, we saw how using for..in
and for..of
loops, combined with type filtering, can provide flexibility when iterating over enums.
As usual, there’s no unique “right” solution. The way you iterate over the key/values of your enums strongly depends on what you have to do and whether you wish to preserve the enum typing.
For more insights, check out these related articles from our blog:
Thanks for reading, and happy coding!
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowAdd to your JavaScript knowledge of shortcuts by mastering the ternary operator, so you can write cleaner code that your fellow developers will love.
Learn how to efficiently bundle your TypeScript package with tsup. This guide covers setup, custom output extensions, and best practices for optimized, production-ready builds.
Learn the fundamentals of React’s high-order components and play with some code samples to help you understand how it works.
Learn about the dependency inversion principle (DIP), its importance, and how to implement it across multiple programming languages.