11. Literal Types

Listen in audio

In TypeScript, literal types are a powerful feature that allows developers to specify exact values a variable can hold. Literal types are particularly useful when you want to enforce stricter checks on the data and ensure that only specific values are allowed, which can significantly reduce bugs and improve code readability.

Literal types in TypeScript can be string literals, number literals, or boolean literals. They are a subset of the broader category of types in TypeScript and provide a way to specify a variable's type as a specific value rather than a more general type like string, number, or boolean.

String Literal Types

String literal types are used to specify that a variable can only be one of a set of specific string values. This is particularly useful when you have a set of predefined options, such as enumeration values or command types.

type Direction = "North" | "South" | "East" | "West";

function move(direction: Direction) {
    console.log(`Moving ${direction}`);
}

move("North"); // Valid
move("Up");    // Error: Argument of type '"Up"' is not assignable to parameter of type 'Direction'.

In the example above, the Direction type can only accept the values "North", "South", "East", or "West". Any other string will result in a compilation error, providing a safeguard against invalid inputs.

Number Literal Types

Number literal types work similarly to string literal types, but they are used for numeric values. This can be useful for defining constants or specific numeric values that a variable can take.

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

function rollDice(roll: DiceRoll) {
    console.log(`You rolled a ${roll}`);
}

rollDice(3); // Valid
rollDice(7); // Error: Argument of type '7' is not assignable to parameter of type 'DiceRoll'.

Here, the DiceRoll type restricts the values to 1 through 6, which are the only valid outcomes when rolling a standard die. This prevents any invalid numbers from being passed to the function.

Boolean Literal Types

Boolean literal types are less common but can still be useful in certain situations. They restrict a variable to be either true or false.

type Truthy = true;

function isTruthy(value: Truthy) {
    console.log("The value is true");
}

isTruthy(true);  // Valid
isTruthy(false); // Error: Argument of type 'false' is not assignable to parameter of type 'true'.

In this example, the function isTruthy only accepts the boolean value true. Attempting to pass false will result in a compilation error.

Combining Literal Types with Union Types

Literal types can be combined with union types to create more complex type definitions. This allows for a flexible yet precise way to define the values a variable can accept.

type Status = "success" | "error" | "loading";

function handleStatus(status: Status) {
    switch (status) {
        case "success":
            console.log("Operation was successful.");
            break;
        case "error":
            console.log("There was an error.");
            break;
        case "loading":
            console.log("Loading...");
            break;
        default:
            const exhaustiveCheck: never = status;
            console.error(`Unhandled case: ${exhaustiveCheck}`);
    }
}

handleStatus("success"); // Valid
handleStatus("failure"); // Error: Argument of type '"failure"' is not assignable to parameter of type 'Status'.

In this example, the Status type is a union of string literals representing different states of an operation. The function handleStatus handles each possible status and uses a switch statement to ensure all cases are covered. The default case uses a never type to catch any unhandled cases, providing an additional layer of type safety.

Literal Inference

TypeScript can infer literal types in certain situations. When a variable is initialized with a literal value, TypeScript can infer its type as a literal type.

let direction = "North"; // Type inferred as "North"

direction = "South"; // Error: Type '"South"' is not assignable to type '"North"'.

In this case, the variable direction is inferred to have the type "North", and any attempt to assign a different string will result in an error. To allow direction to be any of the possible directions, you would need to explicitly specify the type:

let direction: Direction = "North";

direction = "South"; // Valid

Literal Types in Function Parameters and Return Types

Literal types can also be used in function parameters and return types to enforce specific values. This is useful for functions that should only accept or return certain values.

function getStatusMessage(status: Status): string {
    switch (status) {
        case "success":
            return "Operation was successful.";
        case "error":
            return "There was an error.";
        case "loading":
            return "Loading...";
    }
}

const message = getStatusMessage("success"); // Valid
const invalidMessage = getStatusMessage("completed"); // Error

In this example, the function getStatusMessage accepts a Status type and returns a corresponding message string. Attempting to pass a value not defined in the Status type results in a compilation error.

Literal Types with Interfaces and Type Aliases

Literal types can be used within interfaces and type aliases to define more complex structures. This allows for precise type definitions in more extensive codebases.

interface User {
    name: string;
    role: "admin" | "user" | "guest";
}

function assignRole(user: User, role: User["role"]) {
    user.role = role;
}

const user: User = { name: "Alice", role: "user" };
assignRole(user, "admin"); // Valid
assignRole(user, "superuser"); // Error

In this example, the User interface includes a role property with a literal type that restricts the possible roles a user can have. The assignRole function uses this type to ensure only valid roles are assigned.

Benefits of Using Literal Types

Using literal types in TypeScript provides several benefits:

  • Type Safety: Literal types enforce strict checks on the values a variable can hold, reducing the risk of runtime errors.
  • Code Readability: Literal types make it clear what values are expected, improving code readability and maintainability.
  • Documentation: Literal types serve as a form of documentation, indicating the specific values a function or variable can accept.
  • Refactoring: With literal types, refactoring is safer because the compiler will catch any mismatched values.

Conclusion

Literal types in TypeScript are a valuable tool for creating robust and maintainable code. By specifying exact values for variables, developers can enforce stricter type checks, reduce bugs, and enhance code clarity. As you continue to work with TypeScript, consider using literal types to define precise and expressive type definitions that reflect the specific needs of your application.

Now answer the exercise about the content:

What is the primary benefit of using literal types in TypeScript?

You are right! Congratulations, now go to the next page

You missed! Try again.

Article image Enums in TypeScript

Next page of the Free Ebook:

12Enums in TypeScript

7 minutes

Earn your Certificate for this Course for Free! by downloading the Cursa app and reading the ebook there. Available on Google Play or App Store!

Get it on Google Play Get it on App Store

+ 6.5 million
students

Free and Valid
Certificate with QR Code

48 thousand free
exercises

4.8/5 rating in
app stores

Free courses in
video, audio and text