Understanding TypeScript's Handling of Object Literal Types
A look at TypeScript's excess property checking and the nuances of how object literal types behave.

Did you know that TypeScript’s handling of object literals can sometimes lead to unexpected behavior? when I started learning Typescript, I stumbled upon an issue that made no sense to me at first because it was according to me, inconsistent behavior of Typescript.
To set the context, Typescript is a strongly typed programming language that serves as a superset of Javascript, providing type safety and advanced features for building modern web applications. Typescript extends Javascript’s capabilities by introducing optional static typing, allowing developers to catch errors early in the development process and write more reliable code.
Basically, it helps with situations like these (hint of obviously being more nuanced):-


So, moving on to the topic of the blog, the seemingly inconsistent handling of object literals by typescript. What I mean by this is there might be some inconsistencies one might observe in Typescript’s behavior while handling objects, allow me to explain.
Describing the issue
This is a basic function that will be used to create a user, although this is an empty function as there is no functionality written for the function to perform, let us assume there is indeed a functionality written as that is not the issue at hand here. The issue starts when we call it using 2 distinct methods which are pretty much the same with slight differences.

The error arises as we are passing an extra argument called email along with the expected parameters(name and isPaid) to the function which is not expected because it was not defined when we declared the function, pretty basic, right?
Press enter or click to view image in full size

But this error starts making no sense with a slight modification to how we pass the object to the function as an argument, what we are doing is assigning the object we passed directly to the function before to a variable now and then passing the variable to the function, it should have given the same error as before but it sends no error.
Press enter or click to view image in full size

This was the inconsistency I was talking about, and I would like to discuss what are the reasons for this to happen, also the things we can do to prevent this from happening.
Reasons for this to happen
The error occurs due to Typescript’s design decisions, which are Excess Property Checking and Structural Typing in Typescript.
The crux of this behavior lies in how TypeScript performs excess property checks. When you pass an object literal directly, TypeScript performs a strict excess property check to ensure that no extra properties are being passed to the function. This is done to catch potential errors early.
On the other hand, when the object is first assigned to a variable, TypeScript skips this strict check. The reasoning is that the object might be used elsewhere in the code with those extra properties, and TypeScript allows more flexibility in such cases. This is known as structural typing, where TypeScript checks only the required properties and ignores any additional properties when the object is not an immediate literal
As they say

When you first assign the object to variable and pass it to the function, TypeScript utilises structural typing. This allows objects to have extra properties as long as they conform to the expected type structure. This approach is more permissive and flexible.
Benefits:-
Flexibility: It allows objects to be more versatile and used in various contexts. Developers can create objects with additional properties without causing errors in unrelated parts of the code.
Convenience: This feature enables easier object construction and manipulation, especially when dealing with complex data structures or when extending objects with additional information for different purposes.
Type Safety: While being flexible, TypeScript still ensures that required properties are present and have the correct types, providing a balance between flexibility and type safety.
How to ensure stricter type safety and restrict errors like these?
Method 1: Annotate types explicitly with variable
Press enter or click to view image in full size

Here we are explicitly declaring the variable with a specific type to enforce that no additional properties are present.
Method 2: Use strict typing while setting function parameters
Press enter or click to view image in full size

Here the parameters are strictly typed when the function is being made, which prompts TypeScript to throw an error related to unknown properties.
Method 3: Use interface types
Press enter or click to view image in full size

Here we are using interfaces for type safety, and extending the base interface to include additional properties for use cases that need it.
But here we can see that when using the base interface with an additional property, TypeScript throws an error about unknown properties, which can be easily mitigated depending on the context, serving 2 purposes:-
- If the context does require an additional email property, then the extended interface can be used to mitigate the error.
- If the context doesn’t require an additional email property, then interface serves as a correct type checker telling us mistakes we made while setting an email parameter incorrectly.
Conclusion
TypeScript’s handling of object literals, particularly the difference between direct object literals and variables, is a deliberate design choice that balances flexibility with type safety. Understanding this behavior helps in writing more robust TypeScript code and using its features effectively. By following the methods outlined above, you can ensure stricter type safety and avoid potential pitfalls related to excess properties. While it might seem inconsistent at first (it did to me), recognizing that this is a feature designed to provide flexibility will help you better leverage TypeScript in your development process.