🍕 Building a Pizza Like Jetpack Compose: A Modifier Story

Just like the LEGO pieces add toppings and a crust to this pizza, modifiers allow you to customize a Composable with styling, layout, and behavior
I love pizza. And I love Jetpack Compose. Turns out, they have a lot in common. Stick with me, and I’ll show you why building a pizza can teach you how Compose modifiers actually work.
Modifiers: The Secret Sauce
In Compose, every UI element — a Text, a Box, a Column — can be decorated with modifiers. Think of a modifier as a little instruction: “Add padding here,” or “Make this background red.” You can chain these modifiers:
Modifier
.padding(8.dp)
.background(Color.Red)
Each modifier is applied in sequence, just like adding ingredients to a pizza.
Step 1: Building the Chain
I created a Chain interface — our version of Compose’s Modifier. Each step, like adding dough, sauce, or cheese, is a ChainUnit. And just like Compose, we can chain them with then():
Chain.pizzaDough("Thin Crust")
.pizzaSauce("Tomato Basil")
.pizzaTopping("Mushroom")
.pizzaCheese("Mozzarella")
Here’s the magic:
Without fold, we just have a list of instructions. Fold executes them, building the final pizza.
Step 2: Fold — The Assembly Line
Think of fold like an assembly line in a pizza shop:
1. Dough chef lays down the crust
2.Sauce chef spreads the sauce
3.Topping chef sprinkles mushrooms
4.Cheese chef adds mozzarella
Each chef doesn’t need to know about the others — they just follow the sequence. This is exactly what fold() does
chain.fold(Pizza()) { pizza, unit ->
when(unit) {
is Dough -> pizza.dough = unit
is Sauce -> pizza.sauce = unit
is Topping -> pizza.toppings.add(unit)
is Cheese -> pizza.cheese = unit
}
pizza
}
Order matters. Fold ensures the pizza is assembled step by step, just like Compose applies modifiers in order.
Step 3: Scope-Specific Modifiers
Some pizzas are special. Pizza Hut likes stuffed crust, and Dominos loves double cheese. We can model this using scopes, similar to BoxScope or ColumnScope in Compose:
PizzaHutScope().run {
Chain
.pizzaDough("Thin Crust")
.pizzaSauce("Marinara")
.pizzaTopping("Pepperoni")
.pizzaCheese("Mozzarella")
.stuffedCrust() // only valid in PizzaHutScope
.let { Pizza.compose(it) }
}
Step 4: Why Functional Chains Beat Builders
You might wonder, “Why not just use a builder?” Builders mutate objects step by step, which is fine for small things. But functional chains shine when:
Compare:
// Builder pattern
PizzaBuilder().setDough(...).setSauce(...).addTopping(...).build()
// Functional chain
Chain.pizzaDough(...).pizzaSauce(...).pizzaTopping(...).fold(Pizza()) { ... }
The chain reads like a recipe. It’s declarative, composable, and fits perfectly with scopes.
Step 5: Putting It All Together
Here’s a Pizza Hut example:
val pizzaHut = PizzaHutScope().run {
Chain
.pizzaDough("Thin Crust")
.pizzaSauce("Marinara")
.pizzaTopping("Pepperoni")
.pizzaCheese("Mozzarella")
.stuffedCrust()
.let { Pizza.compose(it) }
}
println(pizzaHut.describe())
See how naturally it reads? You assemble a pizza step by step, respecting scope rules, just like Compose applies modifiers to UI elements.
🔑 Takeaways
In short, building a pizza like this feels like Compose in action. You can chain, scope, and fold ingredients — or modifiers — creating a final product that’s perfectly layered, predictable, and reusable.
You’ve seen how we can build a pizza step by step using fold-based chains, scope-specific modifiers, and a Compose-inspired DSL. If you want to play with the code yourself, experiment, or tweak it, I’ve put the entire Kotlin sample in a GitHub Gist.
Go ahead, copy it, run it, and make your own pizza combinations: https://gist.github.com/arunpandian22/3ae30ee154a223a4137cf64f13732659
Have fun exploring, and may your pizza — and your code — always come out perfectly! 🍕👩‍💻