Literal Types for Status Codes: Enforcing Constraints in TypeScript
Learn how to use TypeScript Literal Types to restrict status codes to a specific set of values, preventing invalid state assignments and catching bugs early.

Previously in this course, we explored Union Types for Flexibility in TypeScript, which allowed us to permit multiple types for a single variable. While that approach handles variety, it doesn't solve the problem of "invalid data." If you define a status as a string, any string—even "deleted-but-not-really"—becomes a valid status.
Literal Types take us a step further. They allow us to restrict a variable not just to a type (like string or number), but to a specific, hard-coded set of values.
Defining Status Literal Types
In TypeScript, a literal type is a type that represents a single, exact value. For our task API client, we don't want a task to have just any string as a status; we want it to be either 'pending', 'in-progress', or 'completed'.
Instead of using the broad string type, we define a custom type using the pipe operator (|) to combine specific string literals:
TYPESCRIPTtype TaskStatus = CE9178">'pending' | CE9178">'in-progress' | CE9178">'completed'; interface Task { id: number; title: string; status: TaskStatus; // This now only accepts the three strings above }
By defining TaskStatus as a set of specific literals, we transform the status field from a "free-for-all" string into a strictly controlled constraint.
Enforcing Status Constraints
When you assign a value to a property typed as a literal, the TypeScript compiler checks that value against your defined set. If you try to assign something else, the build will fail.
TYPESCRIPTconst myTask: Task = { id: 1, title: "Finish the API client", status: CE9178">'pending' // Valid }; // This will trigger a compiler error: // Type CE9178">'"archived"' is not assignable to type CE9178">'TaskStatus'. myTask.status = CE9178">'archived';
This is significantly more robust than relying on runtime checks or documentation to tell developers which status codes are valid. The editor will even provide autocomplete suggestions for the allowed values, effectively documenting your API's constraints through the type system itself.
Preventing Invalid Assignments
In a real-world API client, you'll often receive data from an external source that might contain unexpected values. Using literal types, you can create a boundary where you validate incoming data before allowing it into your application logic.
If you are interested in the semantics behind why we choose specific states for our tasks, it helps to understand HTTP Status Codes: Success Semantics for Clean API Design, which often map to the internal state transitions our client handles.
Hands-on Exercise
Update your current task API project to use a literal type for task priority.
- Create a new type alias named
TaskPriority. - Assign it these three literal values:
'low','medium','high'. - Add a
priorityfield to yourTaskinterface using this new type. - Try to create a task object with a priority of
'urgent'—verify that TypeScript throws an error.
Common Pitfalls
- Forgetting the Union: It is easy to accidentally type
type Status = 'pending' | 'completed'and then wonder why'in-progress'is failing. Ensure every valid state is explicitly included in the union. - Case Sensitivity:
'Pending'(capitalized) is not the same as'pending'. Literal types are case-sensitive. If your API sends capitalized statuses, you must include them in your type (e.g.,'Pending' | 'pending'). - Over-generalization: Don't be tempted to fall back to
stringjust because you want to "support future statuses." If you need to add a new status, add it to the literal union. This ensures the rest of your app remains aware of the new state.
FAQ
Can I mix numbers and strings?
Yes. You could define type ResponseCode = 200 | 404 | 500.
Does this affect the runtime JavaScript? No. Literal types are erased during compilation. They exist purely to help you write code with confidence during development.
Is this better than an Enum? Enums can be confusing due to their runtime behavior. For simple status sets, literal union types are the modern, standard way to handle restricted values in TypeScript.
Recap
We've moved from loose primitives to strict, domain-specific constraints. By using Literal Types, you enforce Status Constraints that act as a safety net, ensuring your application only ever works with valid data states. This prevents bugs at the source and makes your code self-documenting.
Up next: We will look at how to use these types effectively in your logic with Type Narrowing with Conditionals.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.


