Question
In TypeScript, what is the correct way to loop through the members of an enum?
I am using this enum:
export enum MotifIntervention {
Intrusion,
Identification,
AbsenceTest,
Autre
}
And in my class I am doing this:
export class InterventionDetails implements OnInit {
constructor(private interService: InterventionService) {
for (let motif in MotifIntervention) {
console.log(motif);
}
}
}
The output is:
0
1
2
3
Intrusion
Identification
AbsenceTest
Autre
I want the loop to run only once for each enum member, because the enum has only four elements. I do not want the 0, 1, 2, and 3 entries, which appear to be numeric indexes from the enum.
Short Answer
By the end of this page, you will understand why looping over a numeric enum in TypeScript shows both numbers and names, how enum reverse mapping works, and how to get only the values you want for tasks like rendering radio buttons.
Concept
TypeScript enums are compiled into JavaScript objects. For numeric enums, TypeScript creates a two-way mapping:
- from name to number
- from number back to name
For example, this enum:
enum MotifIntervention {
Intrusion,
Identification,
AbsenceTest,
Autre
}
becomes conceptually similar to:
{
0: "Intrusion",
1: "Identification",
2: "AbsenceTest",
3: "Autre",
Intrusion: 0,
Identification: 1,
AbsenceTest: 2,
Autre: 3
}
That is why a for...in loop prints both numeric keys and string keys.
This matters because enums are often used for:
- form options
- dropdowns
- radio buttons
- status values
- API-related constants
If you do not understand the reverse mapping behavior, you may accidentally render duplicate options or process the wrong values.
The main idea is:
- numeric enums include reverse mappings
- string enums do not
- when looping, you usually need to filter the keys or values
For display in UI elements such as radio buttons, developers often extract only the string names or only the actual enum values, depending on what the UI needs.
Mental Model
Think of a numeric enum like a bilingual dictionary:
- one side says
Intrusion -> 0 - the other side says
0 -> Intrusion
When you loop through the whole object, you are opening both sides of the dictionary, so you see entries in both directions.
If you only want labels for radio buttons, you should read just one side of the dictionary instead of both.
Syntax and Examples
The most common way to loop through only the names of a numeric enum is to filter out numeric keys.
enum MotifIntervention {
Intrusion,
Identification,
AbsenceTest,
Autre
}
const names = Object.keys(MotifIntervention).filter(key => isNaN(Number(key)));
console.log(names);
// ["Intrusion", "Identification", "AbsenceTest", "Autre"]
Why this works
Object.keys(...)returns all keys from the enum object- numeric enums contain keys like
"0","1", as well as"Intrusion" filter(key => isNaN(Number(key)))keeps only the non-numeric keys
Example for radio buttons
enum MotifIntervention {
Intrusion,
Identification,
AbsenceTest,
Autre
}
const motifOptions = Object.keys(MotifIntervention)
.filter(key => isNaN(Number(key)));
for (const option of motifOptions) {
console.log(option);
}
Output:
Intrusion
Identification
AbsenceTest
Autre
If you want numeric values instead
const values = Object.values(MotifIntervention)
.filter(value => typeof value === "number");
console.log(values);
// [0, 1, 2, 3]
String enum alternative
If you define the enum as a string enum, looping becomes simpler because there is no reverse mapping:
enum MotifIntervention {
Intrusion = "Intrusion",
Identification = "Identification",
AbsenceTest = "AbsenceTest",
Autre = "Autre"
}
console.log(Object.keys(MotifIntervention));
// ["Intrusion", "Identification", "AbsenceTest", "Autre"]
This is often easier for display purposes.
Step by Step Execution
Consider this code:
enum MotifIntervention {
Intrusion,
Identification,
AbsenceTest,
Autre
}
const allKeys = Object.keys(MotifIntervention);
console.log(allKeys);
const namesOnly = allKeys.filter(key => isNaN(Number(key)));
console.log(namesOnly);
Step 1: The enum is created
TypeScript builds an object that contains:
{
0: "Intrusion",
1: "Identification",
2: "AbsenceTest",
3: "Autre",
Intrusion: 0,
Identification: 1,
AbsenceTest: 2,
Autre: 3
}
Step 2: Object.keys reads all keys
allKeys becomes something like:
["0", "1", "2", "3", "Intrusion", "Identification", "AbsenceTest", "Autre"]
Step 3: Filter removes numeric keys
For each key:
"0"→Number("0")is0→ numeric → remove it"1"→ numeric → remove it"Intrusion"→Number("Intrusion")isNaN→ keep it"Identification"→NaN→ keep it
Step 4: Final result
namesOnly becomes:
["Intrusion", "Identification", "AbsenceTest", "Autre"]
That is the list you would typically use to render radio buttons or a select menu.
Real World Use Cases
Enums are often looped over when you need to build UI or enforce a controlled set of values.
Common use cases
- Radio buttons for predefined choices such as status or category
- Dropdown menus for user-selectable options
- Validation rules where input must match one of several allowed values
- API payloads where only known values are accepted
- Configuration settings such as themes, roles, or modes
Example: rendering radio button options
const motifOptions = Object.keys(MotifIntervention)
.filter(key => isNaN(Number(key)));
You can then bind motifOptions in your component template.
Example: validating input
function isValidMotif(value: string): boolean {
return Object.keys(MotifIntervention)
.filter(key => isNaN(Number(key)))
.includes(value);
}
This helps ensure only valid enum names are accepted.
Real Codebase Usage
In real projects, developers usually do not loop over enums directly inside random parts of the code every time. Instead, they create a reusable list of options.
Common pattern: build UI options once
enum MotifIntervention {
Intrusion,
Identification,
AbsenceTest,
Autre
}
const motifOptions = Object.keys(MotifIntervention)
.filter(key => isNaN(Number(key)))
.map(key => ({
label: key,
value: MotifIntervention[key as keyof typeof MotifIntervention]
}));
This produces objects like:
[
{ label: "Intrusion", value: 0 },
{ label: "Identification", value: 1 },
{ label: "AbsenceTest", value: 2 },
{ label: "Autre", value: 3 }
]
Why this pattern is useful
- keeps templates simpler
- avoids repeating filtering logic
- works well for forms and components
- makes testing easier
Guard clause idea
If enum values come from user input or APIs, developers often validate them before using them:
function toMotif(value: number): MotifIntervention | null {
if (MotifIntervention[value] === undefined) {
return null;
}
return value as MotifIntervention;
}
Real project tip
If the enum is mainly for display text, a string enum or a plain array of option objects is often easier to maintain than a numeric enum.
Common Mistakes
1. Using for...in without understanding enum reverse mapping
Broken example:
for (const key in MotifIntervention) {
console.log(key);
}
This prints both numeric and string keys for numeric enums.
Fix: filter the keys.
for (const key of Object.keys(MotifIntervention).filter(k => isNaN(Number(k)))) {
console.log(key);
}
2. Assuming enums behave like arrays
Broken example:
console.log(MotifIntervention.length);
Enums are objects, not arrays. They do not have an array length.
Fix: use Object.keys(...) and filter if needed.
3. Mixing up enum names and enum values
console.log(MotifIntervention.Intrusion); // 0
console.log(MotifIntervention[0]); // "Intrusion"
Be clear whether you need:
- the display name:
"Intrusion" - the stored numeric value:
0
4. Using numeric enums when string enums would be clearer
If your goal is UI display, numeric enums may add extra work.
Better option:
enum MotifIntervention {
Intrusion = "Intrusion",
Identification = "Identification",
AbsenceTest = "AbsenceTest",
Autre = "Autre"
}
5. Forgetting that older TypeScript code may need simpler patterns
Some modern examples use newer helpers or syntax. If you are working in older TypeScript codebases, Object.keys(...).filter(...) is often the safest readable approach.
Comparisons
| Approach | What it gives you | Good for | Notes |
|---|---|---|---|
for...in on numeric enum | Numeric and string keys | Rarely useful directly | Causes duplicate-looking output |
Object.keys(enum).filter(...) | Enum member names only | Display labels, radio buttons | Common solution for numeric enums |
Object.values(enum).filter(...) | Values only | Getting numeric enum values | Simpler in newer JS environments |
String enum + Object.keys(...) | Clean names only | UI-friendly enums | No reverse mapping |
| Plain array of objects | Full control over labels and values | Forms and component UIs | Often easiest in real apps |
Numeric enum vs string enum
| Enum type | Reverse mapping | Easy to loop for UI | Example value |
|---|---|---|---|
| Numeric enum | Yes | Needs filtering | 0 |
| String enum | No | Yes | "Intrusion" |
Enum vs plain array
| Option | Best when | Example |
|---|---|---|
| Enum | You need named constants in code | statuses, modes, roles |
| Plain array | You mainly need display options | radio buttons, select lists |
Cheat Sheet
Numeric enum
enum MotifIntervention {
Intrusion,
Identification,
AbsenceTest,
Autre
}
Get only enum names
const names = Object.keys(MotifIntervention)
.filter(key => isNaN(Number(key)));
Get only numeric values
const values = Object.values(MotifIntervention)
.filter(value => typeof value === "number");
Build radio button options
const options = Object.keys(MotifIntervention)
.filter(key => isNaN(Number(key)))
.map(key => ({
label: key,
value: MotifIntervention[key as keyof typeof MotifIntervention]
}));
String enum version
enum MotifIntervention {
Intrusion = "Intrusion",
Identification = "Identification",
AbsenceTest = "AbsenceTest",
Autre = "Autre"
}
Key rule
- Numeric enums have reverse mapping.
- String enums do not.
- If looping numeric enums, filter out numeric keys.
FAQ
Why does TypeScript enum iteration show both names and numbers?
Because numeric enums are compiled into objects with reverse mappings. They store both name -> value and value -> name.
How do I loop through only enum names in TypeScript?
Use:
Object.keys(MyEnum).filter(key => isNaN(Number(key)))
How do I loop through only enum values?
For numeric enums, use:
Object.values(MyEnum).filter(value => typeof value === "number")
Are string enums easier to use for display?
Yes. String enums do not create reverse mappings, so they are often simpler for UI display.
Should I use enums for radio button labels?
You can, but in many real applications a plain array of { label, value } objects is even clearer, especially when labels need formatting.
Is for...in wrong for enums?
Not exactly, but for numeric enums it gives both directions of the mapping, which is usually not what you want for display.
Why are the numeric keys strings when looping?
Object keys in JavaScript are strings, so keys like 0 and 1 are returned as "0" and "1".
Mini Project
Description
Create a small TypeScript utility that turns an enum into a list of radio button options. This demonstrates how to safely loop through enum members and prepare data for UI rendering without including unwanted numeric reverse-mapping keys.
Goal
Build a reusable function that converts a numeric enum into a clean array of { label, value } option objects.
Requirements
- Create a numeric enum with at least four members.
- Write a function that extracts only the enum names.
- Convert those names into
{ label, value }objects. - Print the resulting options array.
- Make sure no numeric keys appear in the final output.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.