The problem: using plain string allows invalid values
If a property is typed as string, any string is allowed — including nonsensical values such as "Banana". This can lead to bugs only caught at runtime.
Union literal types: restrict a value to a fixed set of literals
A union of literal types restricts a variable to one of several specified literal values. This keeps invalid values out at compile time while remaining lightweight (no runtime object is created).- Each union member is a literal type (for example the type
"White", notstring). - Literal unions can combine primitives and more complex literal values:
When to use an enum
Enums group related named constants. They are useful when you want a named collection that you can reference across your codebase and benefit from editor completions. Enums also produce a runtime object (which can be numeric or string-valued). Numeric enum example:Use union literal types when you want a compact set of allowed literals (strings/numbers) without an extra runtime object. Use enums when you want a named collection you can reference (with code completion) and potentially map to numeric or string values at runtime.
Putting it together: a typed Duck class
Replace unconstrainedstring types with DuckColor (a union of literals) and DuckType (an enum). The compiler now prevents invalid values and provides better tooling support.
Quick comparison: union literal types vs enums
Summary
- Use union literal types to restrict values to a compact set of allowed literals at the type level.
- Use enums when you want a named runtime object (numeric or string) and stable identifiers across your code.
- Applying these TypeScript features makes your APIs and domain models safer, more self-documenting, and easier to use.