Question
I have many Lovefield tables and matching TypeScript interfaces that describe their columns.
For example:
export interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
I would like to get the property names of this interface as an array of strings, like this:
const IMyTable = ["id", "title", "createdAt", "isDeleted"];
I cannot directly build an object or array from the interface itself, because the table names are obtained dynamically. I need a way to work with the interface properties and produce an array from them.
How can this be achieved in TypeScript?
Short Answer
By the end of this page, you will understand why a TypeScript interface cannot be iterated at runtime, how to use keyof to represent interface keys at compile time, and the practical patterns developers use when they need a real array of property names.
Concept
TypeScript interfaces exist only at compile time. They help the TypeScript compiler check your code, but they are removed when JavaScript is generated.
That is the key reason this does not work:
interface IMyTable {
id: number;
title: string;
}
After compilation, IMyTable does not exist in JavaScript. So you cannot loop over it, inspect it, or convert it into an array at runtime.
What you can do instead
There are two common needs here:
- Get the keys as a type
- Use
keyof IMyTable - This gives a type like:
- Use
type MyTableKeys = keyof IMyTable;
// "id" | "title" | "createdAt" | "isDeleted"
- Get the keys as a real runtime array
- Create a real object or array in code
- Then type it so TypeScript checks that the keys are valid
Example:
const myTableKeys: (keyof )[] = [, , , ];
Mental Model
Think of an interface like a blueprint for a house.
- The blueprint tells builders what the house should look like.
- But you cannot live inside the blueprint.
- Once the house is built, the blueprint is not the house itself.
In the same way:
- an
interfacedescribes the shape of data - but it is not a real JavaScript value
- so you cannot loop over it or read its keys at runtime
If you want a list of keys, you must create a real object or array, just like building the actual house from the blueprint.
Syntax and Examples
keyof for compile-time keys
Use keyof when you want a type representing all property names of an interface.
interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
type MyTableKey = keyof IMyTable;
// "id" | "title" | "createdAt" | "isDeleted"
This is useful for function parameters, constraints, and autocomplete.
A typed array of keys
If you need an actual array, define it manually and let TypeScript validate it.
interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
const myTableKeys: (keyof IMyTable)[] = [
"id",
"title",
,
];
Step by Step Execution
Consider this example:
interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
const myTableKeys: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];
Step by step
1. The interface is declared
interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
This tells TypeScript that any IMyTable object must have those four properties.
2. keyof IMyTable is computed by TypeScript
keyof IMyTable
Real World Use Cases
Database column definitions
Your exact case is a common one: defining valid column names for a table.
const userColumns: (keyof IUser)[] = ["id", "email", "createdAt"];
This can be used for:
- selecting fields
- building filters
- validating sort options
- generating query helpers
API field whitelists
When allowing clients to request specific fields, you often keep a list of allowed keys.
const publicProfileFields: (keyof UserProfile)[] = ["id", "name", "avatarUrl"];
Form processing
Forms often map field names to object properties.
const editableFields: (keyof AccountSettings)[] = ["displayName", "timezone", "language"];
CSV export/import
When exporting data to CSV, developers often define a list of columns.
Real Codebase Usage
In real codebases, developers usually do not try to extract keys from an interface directly. Instead, they choose one of these patterns.
1. Define a typed constant array
This is the simplest and most common solution.
export interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
export const MY_TABLE_KEYS: (keyof IMyTable)[] = [
"id",
"title",
"createdAt",
"isDeleted"
];
Benefits:
- easy to read
- runtime-safe
- checked by TypeScript
2. Use a schema object as the source of truth
Some projects avoid interfaces as the main source and use a real object instead.
export const myTableSchema = {
id: "number",
title: "string",
createdAt: "date",
isDeleted:
} ;
= keyof myTableSchema;
= .(myTableSchema) [];
Common Mistakes
Mistake 1: Expecting an interface to exist at runtime
Broken idea:
interface IMyTable {
id: number;
title: string;
}
console.log(Object.keys(IMyTable));
Why it fails:
IMyTableis a type, not a value- it is removed during compilation
Mistake 2: Confusing keyof with a real array
type MyKeys = keyof IMyTable;
This is only a type union, not something you can loop over.
Wrong expectation:
for (const key of MyKeys) {
// Not possible
}
MyKeys does not exist at runtime.
Mistake 3: Using plain string[] and losing safety
Comparisons
| Approach | Runtime value? | Type-safe? | Best use case |
|---|---|---|---|
interface IMyTable { ... } | No | Yes | Describing object shapes |
keyof IMyTable | No | Yes | Representing allowed property names as a type |
(keyof IMyTable)[] | Yes | Yes | Keeping a real array of valid keys |
Object.keys(realObject) | Yes | Partly | Getting keys from an actual object |
Schema object + keyof typeof | Yes | Yes |
Cheat Sheet
interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
Get keys as a type
type MyTableKey = keyof IMyTable;
// "id" | "title" | "createdAt" | "isDeleted"
Get keys as a real array
const myTableKeys: (keyof IMyTable)[] = [
"id",
"title",
"createdAt",
"isDeleted"
];
Get keys from a real object
const myTableShape = {
id: 0,
title: "",
createdAt: new Date(),
isDeleted: false
};
const keys = Object.(myTableShape) (keyof myTableShape)[];
FAQ
Can TypeScript convert an interface into an array of keys automatically?
No. Interfaces are removed during compilation, so there is nothing to inspect at runtime.
Why does keyof not give me a string array?
Because keyof creates a type union such as "id" | "title", not a runtime value.
What is the best way to keep key names type-safe?
Use a real array typed as (keyof MyInterface)[].
Can I use Object.keys() with an interface?
Not directly. Object.keys() requires a real object, not a type.
How do I avoid repeating interface keys manually?
Use a schema object as the source of truth, then derive both the type and the keys from that object.
Does TypeScript check that my key array contains every property?
No. It checks that listed keys are valid, but it does not ensure the array is complete.
When should I use a schema object instead of an interface?
Use a schema object when you need runtime metadata such as field names, validation rules, or database column configuration.
Mini Project
Description
Build a small TypeScript utility for table column management. The idea is to define a table shape, keep a safe list of valid column names, and validate user-provided field names before using them. This demonstrates the difference between type-level keys and real runtime arrays.
Goal
Create a typed list of valid table columns and a function that checks whether a string is a valid column name.
Requirements
- Define an interface for a table with at least four fields.
- Create a runtime array of valid keys typed with
keyof. - Write a function that checks whether a given string is a valid key.
- Loop through the key array and print each column name.
- Show one valid and one invalid field check.
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.
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.
Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes
Learn why Angular @ViewChild can be undefined, when it becomes available, and how to access child components correctly using lifecycle hooks.