Objects and unions

Restrict values with literal unions

Model a small closed set of allowed strings or numbers instead of accepting every value of the primitive type.

A literal type is a type with exactly one value. 'loading' is a type whose only member is the string 'loading'. On its own that is not very useful. Put a few of them in a union and you get one of the most practical tools in TypeScript.

Here is a status that can only be one of four strings:

type Status = 'idle' | 'loading' | 'success' | 'error'

Use it on a function parameter:

function setStatus(status: Status) {}

setStatus('loading')
setStatus('loadng')

The first call works. The misspelled one fails during checking:

error TS2345: Argument of type '"loadng"' is not assignable to parameter of type 'Status'.

Callers also get autocomplete. Type the opening quote inside setStatus( and the editor lists all four values. You stop guessing which strings the function understands.

Compare this with a plain string parameter. It accepts every spelling, even though the application only knows four values. The union makes the closed set visible in the code, and it turns “which statuses exist?” from a documentation question into something the compiler answers.

Watch for widening

Literal types can widen when values pass through mutable objects:

const request = { status: 'loading' }

Because request.status can be reassigned, TypeScript infers it as string, not 'loading'. Pass request.status to setStatus() and you get:

error TS2345: Argument of type 'string' is not assignable to parameter of type 'Status'.

This one confuses people, because the value is clearly 'loading'. The compiler is not looking at the value. It is looking at the type it inferred for a property that could change.

You have two fixes, depending on what the object should do:

const request: { status: Status } = { status: 'loading' }
const fixed = { status: 'loading' } as const

The annotation keeps status assignable to any of the four Status values. The as const version locks it to exactly 'loading' and makes the property readonly. Choose the annotation when the object changes over time, as const when it never does.

The runtime is still plain strings

Remember that types are erased. At runtime a Status is an ordinary string. Nothing stops a network response from carrying 'cancelled', or complete garbage. Validate outside data before you treat it as a Status. The union is a compile-time contract between parts of your own code, not a filter on the world.

Try this on your own: add a 'cancelled' state to the union and follow the compiler errors to every switch or if that now needs a decision.

Lesson completed