Master Pizza Pricing in Gleam: Complete Learning Path

a pizza slice sign hanging from the side of a building

Master Pizza Pricing in Gleam: A Complete Learning Path

This comprehensive guide teaches you to model and implement complex pricing logic in Gleam. You will master custom types, pattern matching, and functional composition to build a robust, error-resistant pizza pricing calculator, a core skill for developing reliable, real-world applications on the BEAM virtual machine.

Have you ever started coding a feature that seemed simple, only to find it spiraling into a tangled mess of `if/else` statements? A pricing calculator for a pizza shop is a classic example. At first, it's just a base price. Then come different sizes, a dozen toppings, special offers, and suddenly your clean code looks like a bowl of spaghetti. This complexity isn't just ugly; it's a breeding ground for bugs that can cost a business real money.

This is a common pain point for developers. Managing business rules in a way that is both accurate and easy to maintain is a significant challenge. But what if you could use a language's core features to enforce these rules at compile time, eliminating entire classes of errors before your code even runs? That's the promise of Gleam. In this module from the exclusive kodikra.com curriculum, we will transform this chaotic problem into an elegant, type-safe, and incredibly readable solution. You will learn not just how to solve the "Pizza Pricing" problem, but how to think in a way that prevents such problems from ever taking root.


What Exactly Is the "Pizza Pricing" Problem?

At its heart, the "Pizza Pricing" problem is a quintessential exercise in translating real-world business logic into robust, maintainable code. It's a stand-in for any scenario where a final value (like a price) depends on a combination of different attributes and rules. It forces us to move beyond simple variables and confront the need for structured data modeling.

The core components of the problem typically include:

  • Base Prices: The fundamental cost of a pizza, which usually varies by size (e.g., Small, Medium, Large).
  • Variable Modifiers: Costs associated with optional additions. In this case, toppings (e.g., Pepperoni, Mushrooms, Onions), where each might have its own price.
  • Business Rules: The logic that combines these components. For example, the first topping might be free, or certain premium toppings might cost more.
  • Data Aggregation: The final calculation involves summing the base price with the total cost of all selected toppings.

This problem is a fantastic learning tool because it's deceptively simple. A naive approach using primitive types like strings for sizes (`"large"`) and integers for toppings (`1` for pepperoni) quickly breaks down. What happens if a user types `"Large"` or `"LARGE"`? What if they enter an invalid topping ID? These scenarios lead to brittle code filled with defensive checks and potential runtime errors. The Gleam approach, which we will explore in detail, solves this by modeling the domain with precision.


Why Is Gleam the Perfect Tool for This Logic?

Gleam isn't just another programming language; it's a language designed with an emphasis on clarity, safety, and maintainability, running on the battle-tested Erlang BEAM virtual machine. These characteristics make it exceptionally well-suited for encoding complex business rules like pizza pricing. Let's break down why.

Unbeatable Type Safety with Custom Types

The single most powerful feature Gleam brings to this problem is its static type system, specifically custom types. Instead of representing a pizza size with a `String`, which could be anything, we can define a finite set of possibilities.

// In Gleam, we define exactly what a PizzaSize can be.
pub type PizzaSize {
  Small
  Medium
  Large
}

This simple declaration is a game-changer. The compiler now understands what a `PizzaSize` is. It is physically impossible to create a `PizzaSize` with a value of `ExtraLarge` or `small` (lowercase) unless we explicitly define it. This eliminates an entire category of bugs related to invalid data, and it does so at compile time, long before your code reaches production.

Expressive and Exhaustive Pattern Matching

Once you have well-defined types, Gleam's `case` expression allows you to handle logic for each variant in a clean and exhaustive way. Say goodbye to messy `if/else if/else` chains.

import gleam/int

pub fn price_for_size(size: PizzaSize) -> Int {
  case size {
    Small -> 800  // Representing price in cents
    Medium -> 1000
    Large -> 1200
  }
}

The Gleam compiler performs "exhaustiveness checking." If you were to add a new size, say `Family`, to the `PizzaSize` type but forgot to update this `case` expression, Gleam would refuse to compile your code. It would give you a helpful error message telling you exactly which case you missed. This is like having a diligent assistant who ensures you've handled every single possibility, a feature that is invaluable for maintaining complex business logic.

Clarity Through Immutability and Pure Functions

In Gleam, all data is immutable by default. This means that once a value is created, it cannot be changed. This might sound restrictive, but it leads to code that is far easier to reason about. A function that calculates a price won't have unexpected side effects; it will simply take data in and return a new value.

This encourages the creation of small, pure functions that do one thing well. For our pizza problem, we can have a function for the size price, another for the topping price, and a final function that composes them. This functional composition makes the code highly modular, testable, and readable.


How to Implement the Pizza Pricing Calculator in Gleam

Now, let's get practical. We'll build the pizza pricing logic from the ground up, following Gleam's idiomatic patterns. This process involves three main steps: modeling our data, writing pure functions for our rules, and composing them into a final calculator.

Step 1: Define Your Domain with Custom Types

First, we model the "nouns" of our problem. What are the core entities? We have pizza sizes, toppings, and the pizza itself. We'll create a new Gleam module, perhaps named `pizza.gleam`, to house our logic.

// file: src/pizza.gleam

// A finite set of possible sizes. This prevents invalid size values.
pub type PizzaSize {
  Small
  Medium
  Large
}

// A finite set of available toppings.
pub type Topping {
  Pepperoni
  Mushrooms
  Onions
  Bacon
  ExtraCheese
}

// A record representing the complete pizza order.
// It's composed of our well-defined custom types.
pub type Pizza {
  size: PizzaSize,
  toppings: List(Topping),
}

With this code, we've created a precise, type-safe blueprint of our problem domain. A `Pizza` is no longer a loose collection of strings and lists; it's a well-defined structure that the Gleam compiler understands and can validate.

Step 2: Implement the Business Rules with Pure Functions

Next, we implement the "verbs"—the calculations. We'll create small, focused functions for each piece of logic. To avoid floating-point precision issues common with currency, we'll represent all prices in cents as an `Int`.

// file: src/pizza.gleam
import gleam/int
import gleam/list

// ... (custom types from above) ...

// A pure function to get the base price for a given size.
// The `case` expression is exhaustive, thanks to the compiler.
fn price_for_size(size: PizzaSize) -> Int {
  case size {
    Small -> 800  // $8.00
    Medium -> 1000 // $10.00
    Large -> 1200 // $12.00
  }
}

// A pure function to get the price for a single topping.
// We can define different prices for "premium" toppings.
fn price_for_topping(topping: Topping) -> Int {
  case topping {
    Bacon -> 150         // $1.50
    ExtraCheese -> 125   // $1.25
    _ -> 100             // All other toppings are $1.00
  }
}

// A function that calculates the total cost of a list of toppings.
// We use functions from Gleam's standard library to process the list.
fn total_topping_price(toppings: List(Topping)) -> Int {
  toppings
  |> list.map(price_for_topping)
  |> int.sum()
}

Notice how clean and declarative this is. The `total_topping_price` function uses a pipeline (`|>`) to pass the list of toppings through a series of transformations: first, `map` each `Topping` to its price (`Int`), and then `sum` the resulting list of integers. Each function is simple, predictable, and easy to test in isolation.

ASCII Diagram: Price Calculation Data Flow

This diagram illustrates how our `Pizza` data flows through the pure functions to generate the final price. Each step is a clear, predictable transformation.

  ● Input: Pizza{size, toppings}
  │
  ├─> pizza.size
  │   │
  │   ▼
  │ ┌──────────────────┐
  │ │ fn price_for_size│
  │ └─────────┬────────┘
  │           │
  │           ▼
  │         Base Price (Int)
  │
  └─> pizza.toppings
      │
      ▼
    ┌───────────────────────┐
    │ fn total_topping_price│
    └───────────┬───────────┘
                │
                ▼
            Toppings Price (Int)
      │
      ▼
    ┌─────────────────────────┐
    │ Sum(Base, Toppings)     │
    └───────────┬─────────────┘
                │
                ▼
           ● Final Price (Int)

Step 3: Compose the Final Calculator Function

Finally, we combine our small functions into a single public function that serves as the main entry point for our module. This function takes a `Pizza` and returns its total price.

// file: src/pizza.gleam
// ... (all previous code in the file) ...

// The main public function that orchestrates the calculation.
pub fn calculate_total_price(pizza: Pizza) -> Int {
  let base_price = price_for_size(pizza.size)
  let toppings_price = total_topping_price(pizza.toppings)

  base_price + toppings_price
}

And that's it. We have a complete, robust, and maintainable pricing calculator. If the business decides to add a new pizza size, Gleam's compiler will guide us to update the `price_for_size` function, ensuring our logic remains consistent and correct.


Where This Pattern Shines in Real-World Applications

The "Pizza Pricing" model is not just an academic exercise; it's a blueprint for handling rule-based logic in a multitude of professional software domains. The principles of using custom types to model your domain and pure functions to encode your business logic are universally applicable and highly valuable.

  • E-commerce Platforms: Calculating the price of a product with multiple variants like size, color, material, and custom engravings. Each variant can be a custom type, and the final price is composed, just like our pizza.
  • SaaS Billing Systems: Determining a customer's monthly bill based on their subscription tier (`type Tier { Free | Pro | Enterprise }`), number of users, and any add-on features (`type AddOn { AdvancedAnalytics | PrioritySupport }`).
  • Insurance Quoting Engines: Calculating premiums based on a complex set of risk factors. A policy can be modeled as a record with fields for age group, location risk, coverage type, and more, all represented by custom types.
  • Shipping and Logistics: Calculating shipping costs based on package weight, dimensions, destination zone (`type Zone { Domestic | International }`), and selected service level (`type Service { Standard | Express | Overnight }`).
  • Gaming: Calculating damage in a role-playing game (RPG). The final damage could be a function of a character's base stats, weapon type, armor enchantments, and temporary buffs, all of which can be modeled with Gleam's type system.

In all these scenarios, the benefits are the same: increased reliability, easier maintenance, and code that serves as clear documentation for the business rules it implements.


When to Adopt This Type-Driven Model

Knowing when to refactor existing code or start a new project with this pattern is a key skill. You should strongly consider this approach when you identify the following code smells or project requirements:

  1. The Rise of "Stringly-Typed" Code: If you find yourself passing strings around to represent concepts that have a fixed set of possible values (like `"small"`, `"medium"`, `"large"`), it's a major red flag. This is brittle and error-prone.
  2. Deeply Nested Conditionals: A long chain of `if/else if/else` or a nested `switch` statement is a classic sign that your logic is becoming too complex to manage. A `case` expression in Gleam is often a much cleaner alternative.
  3. Fear of Change: If developers on your team are hesitant to add a new option (like a new pizza topping or subscription tier) because they're afraid of breaking something, it means your code is not maintainable. A type-driven approach with compiler checks provides the safety net needed to make changes confidently.
  4. Implicit Business Rules: When the business logic is not explicitly defined in the code but is instead hidden in a series of complex calculations and conditions, it becomes difficult to verify correctness. Modeling the domain with types makes these rules explicit and clear.

ASCII Diagram: The Refactoring Journey

This diagram shows the conceptual flow of refactoring from a fragile, primitive-based approach to a robust, type-safe Gleam model.

    ● Legacy Code
      (Using String, Int)
      │
      ├─> if user_input == "large" ...
      ├─> if user_input == "medium" ...
      └─> // What if input is "Large" or "med"? -> BUG!
      │
      ▼
    ◆ Recognize the need for robustness
      │
      ▼
  ┌───────────────────────────┐
  │ 1. Model the Domain       │
  │    `type PizzaSize { ... }` │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ 2. Isolate Logic in       │
  │    Pure Functions         │
  │    `fn price_for_size(...)` │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ 3. Replace Conditionals   │
  │    with `case` Expressions│
  │    // Compiler guarantees all cases handled
  └────────────┬──────────────┘
               │
               ▼
    ● Type-Safe, Maintainable Gleam Code

Benefits and Trade-offs of This Approach

No architectural pattern is a silver bullet. While the type-driven model is incredibly powerful, it's essential to understand its pros and cons to apply it judiciously.

Pros (Advantages) Cons (Potential Trade-offs)
Compile-Time Guarantees: The compiler acts as a safety net, catching a wide range of logical errors (like unhandled cases) before the code is ever run. Initial Verbosity: For extremely simple logic, defining custom types might feel like boilerplate compared to just using a string or boolean.
Enhanced Readability: The code becomes self-documenting. A function signature like fn calculate(size: PizzaSize) is much clearer than fn calculate(size: String). Learning Curve: Developers new to statically-typed functional languages may need time to become comfortable with concepts like custom types, pattern matching, and immutability.
High Maintainability: Refactoring is safer and easier. Adding a new variant to a custom type will cause the compiler to point out every location in the code that needs to be updated. Less Flexibility at Runtime: This approach is rigid by design. If you need to handle completely dynamic, user-defined categories at runtime, a different pattern may be more suitable.
Simplified Testing: Pure functions are trivial to test. You provide an input and assert that the output matches your expectation, with no need to mock dependencies or manage state. Over-engineering Risk: It's possible to go too deep into modeling a domain that is still in flux. It's best applied to business rules that are relatively stable and well-understood.

Your Learning Path: The Pizza Pricing Module

This kodikra learning path module is designed to give you hands-on experience with the concepts we've discussed. By completing the core challenge, you will solidify your understanding of how to apply Gleam's type system to solve practical problems.

The progression is straightforward. We focus on mastering one central concept at a time. Start with the following exercise to build your foundation:

  • Learn Pizza Pricing step by step: This is the foundational exercise where you will implement the full logic for defining pizza types and calculating their final price based on size and toppings.

Completing this exercise will prepare you for more advanced topics in the full Gleam Learning Roadmap, where you'll tackle problems involving state management, concurrency, and interacting with external systems.


Frequently Asked Questions (FAQ)

What is a custom type in Gleam?

A custom type (also known as an algebraic data type or a sum type) is a way to define a new type that can have one of several distinct variations. For example, type PizzaSize { Small | Medium | Large } creates a new type named PizzaSize that can only ever be one of those three specified values, ensuring type safety.

Why is pattern matching with `case` better than `if/else` for this problem?

Pattern matching with case is superior for two main reasons. First, readability: it clearly lists all possible variants and the logic for each one. Second, exhaustiveness: the Gleam compiler checks to ensure you have written logic for every single variant of your custom type. If you add a new variant later, the compiler will force you to update your `case` expressions, preventing runtime bugs.

How does immutability help in price calculation?

Immutability ensures that data, once created, cannot be changed. In price calculation, this means you can pass a `Pizza` object to a function and be 100% certain that the function will not modify the object in unexpected ways. The function will simply read the data and return a new value (the price). This makes the code predictable, easier to debug, and safe to use in concurrent systems.

Can I use this pattern for more complex rules like "buy one, get one free"?

Absolutely. You would extend your data model. For example, you might have a function that takes a `List(Pizza)` (representing a whole order) instead of a single `Pizza`. This function could then contain logic to sort the pizzas by price and waive the cost of the cheapest one if the list contains two or more items. The core principle of modeling the rules and data with types remains the same.

What's the difference between a `type` and a `pub type` in Gleam?

The `pub` keyword makes the type public, meaning it can be imported and used by other modules in your project. A `type` without `pub` is private to the module it's defined in. For data structures that are part of your application's core domain, like `Pizza`, you'll almost always want to make them `pub` so they can be used across your codebase.

How should I handle prices with cents in Gleam?

The best practice, as shown in the examples, is to avoid using floating-point numbers for currency due to potential precision errors. Instead, store all monetary values as an `Int` representing the smallest unit of the currency (e.g., cents for USD, pence for GBP). You only format it into a decimal string for display to the end-user at the very last moment.

Where can I learn more about Gleam's standard library for lists?

Gleam's standard library is well-documented and essential for effective programming. You can find detailed information about the `gleam/list` module, including functions like `map`, `fold`, `filter`, and more, on the official Gleam language website. These functions are your primary tools for processing collections of data in an immutable way.


Conclusion: From Logic to Type-Safe Art

You have now seen how a potentially messy business problem can be transformed into a clean, elegant, and robust solution using Gleam's powerful type system. The key takeaway is to stop thinking about data as primitive strings and numbers and start modeling your problem domain with the precision of custom types. This shift in mindset, combined with the power of pattern matching and pure functions, allows you to build applications that are not only correct by design but also a joy to maintain and extend.

The "Pizza Pricing" problem is a gateway to mastering this modern, functional approach to software development. The skills you've learned here are directly applicable to countless real-world scenarios. Now it's time to put theory into practice.

Disclaimer: All code snippets and best practices are based on Gleam v1.3.1 and its corresponding standard library. As the language evolves, some function names or module paths may change. Always refer to the latest official Gleam documentation.

Back to Gleam Guide | Explore the full Gleam Learning Roadmap


Published by Kodikra — Your trusted Gleam learning resource.