Literal Types and Enums
A string variable can hold any string. But AceIt’s difficulty can only be 'easy', 'medium', or 'hard'. TypeScript has two tools for expressing this constraint: literal types and enums.
Literal types
Section titled “Literal types”A literal type is a type that represents exactly one value. Instead of string, you write the specific string itself:
const difficulty: 'easy' = 'easy'; // can only ever be 'easy'On its own this is not useful — but combined with a union, it describes a fixed set of allowed values:
type Difficulty = 'easy' | 'medium' | 'hard';
let level: Difficulty = 'easy'; // ✓level = 'medium'; // ✓level = 'expert'; // Error: Type '"expert"' is not assignable to type 'Difficulty'Now TypeScript enforces that only valid difficulty values are used anywhere Difficulty appears. A typo ('medum') is a compile error rather than a silent mismatch that breaks the API request.
Type widening and const
Section titled “Type widening and const”TypeScript widens let variables initialized with a literal to their base type:
let d = 'easy'; // inferred: string, not 'easy'TypeScript assumes a let variable will be reassigned, so it infers string — the widest type that includes 'easy'. If you want a const literal:
const d = 'easy'; // inferred: 'easy' (literal type)const cannot be reassigned, so TypeScript infers the exact literal. This distinction matters when passing values to functions that expect a Difficulty:
let level = 'easy'; // inferred: stringfetchQuestions(10, level); // Error: 'string' is not assignable to 'Difficulty'
const level = 'easy'; // inferred: 'easy'fetchQuestions(10, level); // ✓If you need a mutable variable constrained to a literal union, annotate it explicitly:
let level: Difficulty = 'easy'; // annotated — not widenedlevel = 'hard'; // ✓An enum defines a named set of constants. Unlike a union of literals, enum members have a name and a value — by default numeric, but string enums are common in real code:
enum ResponseCode { Success = 0, NoResults = 1, InvalidParam = 2, TokenNotFound = 3, TokenEmpty = 4,}You refer to members by name rather than by value:
if (data.response_code === ResponseCode.Success) { // process results}
if (data.response_code === ResponseCode.NoResults) { throw new Error('Not enough questions available');}Using ResponseCode.Success instead of the magic number 0 makes the code self-documenting. If the API ever changes a code, you update the enum in one place.
Enums in AceIt’s types.ts
Section titled “Enums in AceIt’s types.ts”AceIt’s types.ts defines both a literal union and an enum:
// Literal union — for values that appear as strings in the UI and APIexport type Difficulty = 'easy' | 'medium' | 'hard';
// Enum — for the API's numeric response codesexport enum ResponseCode { Success = 0, NoResults = 1, InvalidParam = 2, TokenNotFound = 3, TokenEmpty = 4,}Difficulty stays as a string literal union because it appears directly in the URL (&difficulty=easy) — the actual string value matters. ResponseCode is an enum because the raw numbers (0, 1, 2) have no meaning without names, and the enum communicates intent clearly.
When to use literal unions vs enums
Section titled “When to use literal unions vs enums”Use a literal union when:
- The values are strings that appear directly in code or APIs
- You want to keep it simple — no import needed, works in type position only
- The set is small and unlikely to grow
Use an enum when:
- The values are numeric codes with no inherent meaning
- Members need descriptive names that the raw values do not provide
- You want a runtime object (enums exist in the compiled JS; literal unions do not)
Exercise
Section titled “Exercise”In types.ts in your aceit/ folder:
- Define
type Difficulty = 'easy' | 'medium' | 'hard'. Write a functionbuildDifficultyParam(d: Difficulty): stringthat returns&difficulty=${d}. - Define
enum ResponseCodewith the five values above. - Write a function
handleResponseCode(code: ResponseCode): voidthat logs a descriptive message for each code using aswitchstatement. - Declare
let level = 'easy'and try passing it tobuildDifficultyParam. Read the error. Fix it two ways: withconst, and with an explicitDifficultyannotation.
- A literal type represents exactly one value — combined with
|, it describes a fixed set of options. letvariables initialized with a literal are widened to the base type;constpreserves the literal.- Enums define named constants with values — numeric by default, but string enums are common.
- Use literal unions for string values that appear directly in code or APIs.
- Use enums for numeric codes or constants that need descriptive names to be readable.