omiid
homenotebookai usage

The trap of making everything dynamic

March 01, 2024 · Updated on August 09, 2026

Making every value dynamic looks like the flexible choice. TypeScript works the other way: it pushes you to make things known at compile time, not at runtime. In practice, types work best when they can be derived from static, fixed values.

A common place this shows up is computed property names in types and interfaces: the key has to be something TypeScript can treat as a literal type (or a unique symbol).

I write short, practical notes like this one. Get the next one by email:

Unsubscribe anytime.

Computed keys must be literals

A let binding widens to string

Declaring the key with let is enough to break the type:

let myKey = "id"; // TypeScript sees this as type 'string', not specifically "id"
 
type User = {
	[myKey]: number; // ❌ Error: myKey is a general string, not a literal.
};

Because myKey is let, its type is widened to string. TypeScript can’t use a general string as a computed key in a type literal.

const and unique symbol keep the key literal

To fix it, you need myKey to be a value that TypeScript can treat as fixed.

You can make it a const:

const myKey = "id"; // Because it's a const, the type is "id", not 'string'
 
type User = {
	[myKey]: number; // ✅ Works!
};

Or you can use a unique symbol:

const MyIdentifier = Symbol("id"); // This is a unique symbol
 
type Data = {
	[MyIdentifier]: string; // ✅ Works!
};

const alone does not stop nested values from widening

I was adding multiple plugins to a system, and each plugin had its own unique identifier key. I started with a structure like this:

const Plugins = {
	Auth: {
		internalKey: "user_token",
		version: 1,
	},
	Analytics: {
		internalKey: "session_id",
		version: 2,
	},
};

The goal was to create a type that uses each plugin’s internalKey as the property name.

Something like this:

type PluginState = {
	[Plugins.Auth.internalKey]: string;
};

That’s when TypeScript gave me this error:

A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.

Even though Plugins is a const, without as const the nested internalKey values get widened to string. From the type system’s perspective, Plugins.Auth.internalKey is just string, not the literal type "user_token".

as const pins the nested values to literal types

as const is a const assertion. It tells TypeScript to infer the narrowest literal type for every value in the object, nested ones included:

const Plugins = {
	Auth: {
		internalKey: "user_token",
		version: 1,
	},
	Analytics: {
		internalKey: "session_id",
		version: 2,
	},
} as const; // <--- this keeps every nested string literal
 
// Now this is perfectly valid!
type PluginState = {
	[Plugins.Auth.internalKey]: string;
};
 
// Resulting type is effectively: { "user_token": string }

Derive the key union from the config object with keyof typeof

In the end, I wanted to put configurations for multiple items in one config object, then derive a centralized type from the id fields. keyof typeof reads the object’s own keys, and a mapped type turns the resulting union into an object type.

const AppConfig = {
	featureA: { id: "feat_a", active: true },
	featureB: { id: "feat_b", active: false },
} as const;
 
// Create a union of all 'id' values: "feat_a" | "feat_b"
type AllFeatureIds = (typeof AppConfig)[keyof typeof AppConfig]["id"];
 
// Use that union to drive an object type
type FeaturePermissions = {
	[K in AllFeatureIds]: boolean;
};
 
/*
Resulting Type:
type FeaturePermissions = {
    feat_a: boolean;
    feat_b: boolean;
}
*/

When you want TypeScript to lift values into types, especially for computed keys, you have to keep those values literal. const plus as const is usually the whole fix.

Join My Newsletter

Occasional notes on software, tools, and things I learn. No spam.

Unsubscribe anytime.

Continue Reading

  • Stop tuning everything. pgvector has three knobs that matter.Aug 18, 2026
  • Your agent's knowledge base is lying to you. Run these 14 checks.Aug 18, 2026
  • Your agent's knowledge base is rotting in 14 waysAug 17, 2026
  • Claude watermarks its text now. Here's how, and what it can't do.Aug 16, 2026
  • HNSW or IVFFlat? Choosing and building your pgvector indexAug 14, 2026