Patterns
Why does Object.keys(config) give me string[] instead of the keys I can see right there?
Because a value can have more keys than its type declares, and Object.keys returns all of them. Narrowing the return is a deliberate, local decision — not a fix for a bug.
The recipe
type Keys<T extends object> = (keyof T)[] const typedKeys = <T extends object>(value: T): Keys<T> => Object.keys(value) as Keys<T> const config = { retries: 3, verbose: true } const keys = typedKeys(config)
The build compiles this and checks each result below.
How it works
- 01
type Keys<T extends object> = (keyof T)[]The shape you wish
Object.keyshad. Naming it separately keeps the cast in one place and readable. - 02
Object.keys(value) as Keys<T>A cast, and an honest one: you are asserting the object has no keys beyond its type. For a literal you just built that is true; for something that arrived from elsewhere it may not be.
What you get
typeof keys→("retries" | "verbose")[]Keys<{ a: 1; b: 2 }>→("a" | "b")[](typeof keys)[number]→"retries" | "verbose"And indexing by
numberrecovers the union, which is usually what you wanted to iterate over.
Where it goes wrong
The cast is a lie whenever the object came from outside your program. Extra properties are legal — a value typed { a: 1 } may carry b at run time — and this helper will hand you a key list that does not include it while claiming to be complete.
Takeaway
Object.keys returning string[] is not a design mistake; it is the type system being honest about extra properties. Use this helper on objects you built, not on ones you received.

