Introduction: Why is the object literal topic still blowing up Stack Overflow?
\n\nIf you've ever written in TypeScript, you've likely encountered this situation: you create an object, specify a type, and the compiler throws an error, or even worse, doesn't throw an error, but everything crashes at runtime. Sound familiar?
\n\nThe topic of type definition in object literal in TypeScript is one of the most popular queries on Stack Overflow with thousands of views. And this is no coincidence. The apparent simplicity hides many nuances: the difference between interface and type, excess properties, optional fields, readonly modifiers, and the trendy satisfies operator.
In 2025, TypeScript has come a long way. Features have emerged that make code cleaner and safer. But many developers still use outdated approaches. Let's figure out how to properly type object literals today to make code reliable, readable, and modern.
\n\n1. Interface or Type: What to choose for object literal in 2025?
\n\nThis is a classic question that sparks heated debates. Let's set the record straight.
\n\nWhen to use interface
\nInterface is the "gold standard" for describing the shape of objects. It works better with type inference, gives clearer error messages, and supports declaration merging.
\n\n// ✅ Good: interface for object literal\ninterface User {\n name: string;\n age: number;\n email?: string; // optional field\n}\n\nconst user: User = {\n name: "Anna",\n age: 28,\n email: "anna@example.com"\n};\n\nWhen to use type
\nType aliases are indispensable for union types, intersection types, tuples, and complex conditional types. They work for simple objects too, but have limitations.
\n\n// ✅ Good: type for union\ntype Status = "active" | "inactive" | "pending";\n\n// type for object is also ok, but less extensible\ntype UserType = {\n name: string;\n age: number;\n};\n\nPractical advice for 2025: Use interface for public APIs and describing object shapes. Use type for complex compositions, conditional types, and union types. This is not dogma, but good practice.
2. Excess property checks: enemy or friend?
\n\nOne of the most common mistakes for beginners is an extra property in an object literal. TypeScript complains, and this is good.
\n\ninterface Config {\n url: string;\n timeout?: number;\n}\n\n// ❌ Error: excess property 'retries'\nconst badConfig: Config = {\n url: "https://api.example.com",\n timeout: 5000,\n retries: 3 // Error!\n};\n\nBut there is a loophole: if you assign the object via a variable, the check weakens. Be careful.
\n\nconst configData = {\n url: "https://api.example.com",\n timeout: 5000,\n retries: 3\n};\n\n// ✅ No error (but this can be unexpected)\nconst config: Config = configData;\n\nIn 2025, use satisfies for strict checking without losing type inference. More on this in the next section.
3. The satisfies operator: the main feature of 2025
\n\nThe satisfies operator appeared in TypeScript 4.9 and became a real breakthrough. It allows you to check that an expression's type satisfies a certain type, without changing the inferred type itself.
// Old approach: lose the precise type\nconst palette: Record<string, string | string[]> = {\n red: [255, 0, 0],\n green: "#00ff00",\n blue: "#0000ff"\n};\n// palette.red — string | string[] (bad)\n\n// New approach with satisfies\nconst palette = {\n red: [255, 0, 0],\n green: "#00ff00",\n blue: "#0000ff"\n} satisfies Record<string, string | string[]>;\n\n// palette.red — number[] (precise type preserved!)\n// palette.green — string\n// palette.blue — string