TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
176: The object Type
You've likely seen the object type (lowercase 'o') and thought, "Perfect, that's exactly what this is—an object." It feels intuitive. If you're writing a function that takes a configuration object or a set of metadata, object seems like the most honest description of that data. But in TypeScript, object is a bit of a trap. It doesn't mean "an object with properties"; it actually means "anything that isn't a primitive."
The Trap of the Lowercase Object
Let's look at a scenario I've run into a dozen times. Imagine you're building a logging utility that accepts a message and some optional metadata. You want to make sure the metadata isn't just a random string or a number—it has to be a proper object.
function logEvent(message: string, metadata: object) {
console.log(`Event: ${message}`);
// I just want to check if there's a timestamp in here
if (metadata.timestamp) {
console.log(`Occurred at: ${metadata.timestamp}`);
}
}
If you try to run this, TypeScript is going to scream at you. It'll tell you that Property 'timestamp' does not exist on type 'object'. This is where the frustration kicks in. You're looking at the code thinking, "But it IS an object! Why can't I just access a property?"
The reason is that object is a "non-primitive" type. It tells TypeScript that the value isn't a string, number, boolean, symbol, null, or undefined. However, it tells TypeScript absolutely nothing about the members of that object. Since TypeScript can't guarantee that timestamp exists on every single possible non-primitive in existence, it blocks you from accessing it for your own safety.
When You Actually Need to Touch the Data
If you're in a position where you actually need to read properties from the object, object is the wrong tool. Depending on how much you know about the data, you have two better paths. If you know the exact shape, use an interface. But if you're dealing with dynamic data—like a JSON response where the keys vary—you want a Record.
Here is how I'd rewrite that logger to actually be useful:
function logEvent(message: string, metadata: Record<string, unknown>) {
console.log(`Event: ${message}`);
// Now TS knows it's an object with string keys,
// but it doesn't assume what the values are (unknown)
if (metadata.timestamp) {
console.log(`Occurred at: ${metadata.timestamp}`);
}
}
By using Record<string, unknown>, you're telling the compiler: "This is an object, and I can index into it using strings." The unknown part is the secret sauce here. It's safer than any because it forces you to perform type checking or casting before you do anything complex with the value, but it still allows you to access the property in the first place.
Picking the Right Tool
So, when should you actually use object? In my experience, it's rare, but it's useful when you specifically want to prevent primitives. For example, if you're writing a library function that handles object references (like a deep-clone utility or a comparison tool), you don't care what the properties are; you just need to ensure the input isn't a string or a number. In those cases, object is exactly what you want because it explicitly forbids primitives without making any promises about the internal structure.
📋 Practical Task
Refactoring the API Response Validator
You have been handed a piece of code for a response validator. The original author used object for the payload, but the function is failing to compile because it tries to access the status property of that payload.
Refactor the validateResponse function below. Replace the object type with a more appropriate TypeScript type (like a Record) that allows the code to access the status property while maintaining type safety for the values.
function validateResponse(payload: object) {
if (payload.status === 'success') {
return true;
}
return false;
}
// Test cases
validateResponse({ status: 'success', code: 200 }); // Should work
validateResponse({ status: 'error', code: 500 }); // Should work
There are no comments for now.