# The Ilities

The non-functional qualities architecture actually trades between — maintainability, testability, scalability, reliability, observability.

**Leads to:** Database as Architectural Pillar, Microservice Cost Tradeoffs

**Rests on nothing.** A place to start.

*Software Engineering · Software Architecture*


## Why learn this

This is the set of qualities that make a system liveable — the difference between software that is a joy to change and software that is a nightmare to touch. Learning the ilities—maintainability, testability, scalability, reliability, observability—gives you the vocabulary to say what a system is actually good at before it is built. It unlocks the rest of your software architecture journey: every decision you will study—whether to add a layer, invert a dependency, split a microservice, or choose a database—is ultimately a trade between these qualities. Master the ilities now and every later page becomes a conversation about which ilities you are trading, rather than a mystery about why the system feels rigid, slow, or unknowable.


## The intuition

When someone asks 'is this software good?', the first answer people reach for is 'does it do what I asked?'. That is functionality—the features, the buttons, the correct answers. But a system can be functionally perfect and still be a disaster: it takes three weeks to add a simple field, tests are so brittle they break on every change, it slows to a crawl at double the users, it crashes silently at 2 AM, and nobody can tell why it failed. These are not bugs in the features—they are failures of different qualities entirely. The ilities are those qualities that are not about what the system does, but about how well it does it over its lifetime. They are the reason two systems with identical features can be worlds apart in how they feel to build, run, and fix.

Think of a house. A house can have perfect rooms—the right walls, the right windows, the right doors—and still be badly built if the roof leaks, the wiring is tangled, or the pipes are buried without a map. The ilities are like the plumbing and the foundation: invisible when they work, catastrophic when they do not. They are what makes the house habitable, not just presentable.


## An analogy

Imagine you are the chief architect of a city. Your job is to decide where roads go, how buildings connect, and what rules everything follows. You are not building one building—you are designing a place that will be lived in and changed for decades.

You have five distinct pressures, and every one of them is an ility:

- **Maintainability** — can future city planners fix a broken water main without tearing down a whole block? If the city's electrical wiring is a tangled mess, changing one light means risking a blackout.
- **Testability** — before the city is fully built, can you simulate a flood to know whether the levees hold? If the city's layout makes it impossible to run a drill, you will only find out when the real flood comes.
- **Scalability** — what happens when the population doubles? If the single bridge was designed for today's traffic, the city grinds to a halt. If the roads are laid out so new bridges can be added, the city grows.
- **Reliability** — does the power stay on when a storm hits? If one substation is a single point of failure, the whole city goes dark.
- **Observability** — when a fire starts, can anyone tell which building it is in, or are the smoke alarms all silent? If you cannot see inside, you cannot respond.

Now the key insight: you cannot maximise all five at once. A city designed for perfect maintainability—every system modular and self-contained—might be extremely expensive to build, with duplicate infrastructure everywhere. A city designed for maximum scalability—every building connected by a dense road grid—might be a nightmare to maintain or observe, because there are so many connections to track. Every design decision favours some ilities and costs others. The city planner's real job is to decide which qualities matter most for this city's future, and to be honest that the others will be slightly worse.

Where the analogy stops: cities are physical, and adding a road is slow and expensive. Software can change faster, but not infinitely so—decisions made early, like which programming language, which database, or how services are split, are still very costly to reverse. The ilities are the language you use to have that argument honestly, before you commit.


## Definition

The ilities are the non-functional qualities of a software system—maintainability, testability, scalability, reliability, and observability—that determine how well the system can be built, changed, operated, and trusted over time, and which architecture must trade between.


## How it connects

This page is the first course in your software architecture track, and it names the things every later decision trades. When you study **Cohesion and Coupling**, you are really deciding whether a module is maintainable and testable. **Layers** and **Ports and Adapters** are structural patterns chosen because they make a system testable and maintainable. **Dependency Inversion** exists so you can swap implementations—a scalability or testability move. **Databases as an Architectural Pillar** is largely about reliability and scalability, because the schema outlives the code. Every one of those pages will return to these same five qualities.


## The mathematics

Mathematics here is about measurement and comparison, not calculation. The branch of mathematics that helps is discrete mathematics and order theory, because we are comparing relative priorities — is maintainability more important than scalability in this context? We do not need calculus or algebra; we need a way to name and rank things. The operation we use is the comparison of magnitudes: we assign weight or priority to each ility, and we reason about tradeoffs as a balance. For example, if we have a choice between making a system faster (scalability) but harder to change (maintainability), we compare the two. We might formalise this with a simple matrix: each option scores on each ility, and we sum the scores to decide. That is arithmetic, not algebra. The key mathematical idea is that these are competing constraints, and we can use a priority table to make the tradeoff explicit.

For each candidate architecture, compute the weighted score S = Σᵢ wᵢ sᵢ, where wᵢ is the importance of ility i, and sᵢ is the score of architecture on that ility (1-5). Choose the architecture with the highest S.


## A worked example

```
Suppose you are choosing between two architectures: a monolith and microservices. You score each on a scale of 1 to 5 for each ility: maintainability, testability, scalability, reliability, observability.

Step 1: Monolith: maintainability 4, testability 3, scalability 2, reliability 5, observability 4   # The monolith is easy to maintain because everything is in one codebase, but hard to scale because you must scale the whole thing. Reliability is high because there are fewer moving parts.
Step 2: Microservices: maintainability 3, testability 4, scalability 5, reliability 2, observability 3   # Services can be maintained independently, but distributed failures lower reliability. Scalability is high because you can scale services separately.
Step 3: Average monolith = (4+3+2+5+4)/5 = 18/5 = 3.6   # Sum the scores and divide by 5 to get an average readiness.
Step 4: Average microservices = (3+4+5+2+3)/5 = 17/5 = 3.4   # The monolith scores slightly higher on average, but the real decision depends on which ilities matter most for your use case.

Result: If you weigh reliability and maintainability as critical, you might choose the monolith. If you need to scale rapidly and have a strong DevOps team to handle complexity, microservices might be better. The numbers make the tradeoff explicit.
```


## From scratch, in Python

```python
from dataclasses import dataclass
from typing import Dict, List

# --- Python building blocks: lists, dictionaries, functions, classes, loops ---

# A list is a collection of things in order, like a queue at a shop.
prices = [100, 50, 200]   # three numbers in a row
print(prices[0])          # 100 (counting starts at 0)

# A dictionary looks up a value by a name, like a phone book.
user = {"name": "Ananya", "age": 28, "city": "Mumbai"}
print(user["name"])       # "Ananya"

# A function is a box that takes inputs and gives an output, like a vending machine.
def double(x):
    return x * 2
print(double(5))          # 10

# A class is a blueprint for making many similar things with their own data and actions.
@dataclass
class User:
    name: str
    age: int
    city: str

ananya = User(name="Ananya", age=28, city="Mumbai")
print(ananya.name)        # "Ananya"

# A for loop repeats an action for each item in a list.
total = 0
for price in prices:
    total += price
print(total)              # 350

# --- The real work: a function that scores a system on the five ilities ---

# This function takes a description of a system as input. The description
# is a dictionary where keys are the five ilities and values are scores
# from 1 (poor) to 5 (excellent).

def evaluate_system(system: Dict[str, int]) -> Dict[str, str]:
    """Return a dictionary with useful messages about each ility."""
    result = {}
    for ility, score in system.items():
        if score >= 4:
            result[ility] = "strong"
        elif score == 3:
            result[ility] = "acceptable"
        else:
            result[ility] = "weak"
    return result

# Let's test a system that is very testable but not scalable.
my_system = {
    "maintainability": 4,
    "testability": 5,
    "scalability": 2,
    "reliability": 3,
    "observability": 4,
}

verdicts = evaluate_system(my_system)
for il, v in verdicts.items():
    print(f"{il}: {v}")

# Notice that the requirements (what we want) are exactly these qualities.
# The 'ilities' are not code; they are criteria. We can model them in code,
# but the real tradeoff happens when we design the system.
```


## The same thing, with a framework

```python
In practice, architects do not code the ilities. They use them in design documents, tradeoff reports, and architecture decision records. But to give a concrete sense, there are Python libraries for each quality: `hypothesis` for property-based testing (testability), `scalene` for profiling (scalability), `prometheus_client` for metrics (observability). These are tools, not the concept itself.
```


## What the code does

First, we introduce the three core Python structures we need. **Lists** hold items in order—`prices = [100, 50, 200]`—and we use them when order matters. **Dictionaries** map keys to values—`user = {"name": "Ananya", ...}`—perfect for the five ilities, because each ility is a named quality. **Functions** take input and return output—`double(5)` gives `10`—letting us reuse logic. **Classes** let us create many objects with the same shape—`User` makes a record for a person. **For loops** iterate over a collection—we sum the prices.

Then the real function, `evaluate_system`, takes a dictionary of ilities and their scores, and returns a dictionary saying whether each is 'strong', 'acceptable', or 'weak'. It loops over each item in the dictionary. This is a toy illustration, but it shows the principle: the ilities are criteria that can be measured and discussed. The code itself does not enforce the ilities; it is a way to record a judgement.


## Common misconceptions

**The ilities are extras, not real requirements** — My manager only cares about features. The ilities are nice-to-haves we can add later.

Users experience the ilities directly: a slow app is a bad app, even if it has every button. And the cost of adding ilities later is enormous—retrofitting scalability is far more expensive than engineering it in from the start. In fact: Ilities are the real requirements that get tested in production. A feature that cannot be deployed quickly, runs slowly, or crashes is not a feature—it is a liability.

**All ilities are equally important** — A good architecture should score high on every ility.

If you try to have every quality, you end up with a system so over-engineered it is slow to develop and hard to change. Every choice has a cost; the art is choosing what to spend on. In fact: Ilities conflict. Maximising one often costs another. Good architecture is an explicit tradeoff that favours the qualities that matter for the system's goals.

**Ilities are automatically fixed before launch** — We'll make it scalable when we get big users. For now, let's just build the features.

Some qualities, like scalability and reliability, are emergent properties of the whole system's structure. You cannot bolt them on; you must bake them in. In fact: Scalability is a design decision, not a patch. If the data store, the API, and the processing pipeline are not designed to scale, adding hardware later does not help—the bottleneck is in the code.

**Ilities are just about code** — As long as the code is clean, we have good ilities.

A clean codebase with no monitoring is still unobservable. A perfect test suite that only runs on a developer's machine is not continuously tested. The ilities are system-level properties, not code-level properties. In fact: Ilities depend on the entire system: the team processes, the infrastructure, the organisational structure, and the operational runbook. Code quality is necessary but not sufficient.


## In short

The ilities are maintainability, testability, scalability, reliability, and observability. They are the real requirements of any system, and they conflict with each other. A good architect names the tradeoff and decides deliberately which ilities to favour, based on what the system must survive.


## Where this is used

Where do the ilities show up in building AI systems? Everywhere, but they show up differently in each setting.

**Large language models.** The models themselves are enormous—billions of parameters trained on thousands of GPUs. Here the ilities are primarily about the infrastructure and the training pipeline. **Scalability** is the whole game: the training code must spread across thousands of GPUs and keep the clusters busy; if it does not scale, the training run takes impossibly long. **Reliability** is vital because a training run can take months, and a single machine failure can ruin the entire run—the framework must detect and recover from failures automatically. **Observability** is needed to monitor loss curves, GPU utilisation, and checkpoints—engineers watch live dashboards to catch divergence. **Maintainability** and **testability** apply to the code that prepares data, runs the pipeline, and evaluates the model—the model is frozen, but the surrounding code changes constantly.

**Small models on devices.** When the model runs on a phone or a smart speaker, the ilities change flavour. **Maintainability** is about being able to update the model and the app without a full reinstall. **Testability** is crucial because on-device code has millions of combinations of hardware and OS versions; you cannot test them all, so you need tests to simulate the important ones. **Scalability** in the sense of user load is less about the model itself and more about the update server—millions of devices need to download a new model at once. **Reliability** matters because the device may be offline, so the model must work with stale data. **Observability** is tricky because you cannot put a debugger on a user's phone; you need to log events and send them back.

**Retrieval-augmented generation (RAG).** RAG combines a generative model with a knowledge base. The ilities crop up in the retrieval component. **Testability** is critical because you need to test whether the retriever returns the right documents for a query. **Reliability** means the system must give sensible answers even when the data store is slow or down. **Scalability** is the challenge of indexing a large corpus and querying it fast enough. **Observability** lets you trace a query from the user to the retrieved documents to the final answer, so you can debug why a prompt produced a bad answer. **Maintainability** matters because the knowledge base changes over time—you need to update embeddings without breaking the system.

**Agentic systems.** These are systems that plan, call tools, and act in loops. Here the ilities are survival. **Reliability** is the hardest—the agent can take actions in the real world (send an email, run a command), so a wrong step is not just a bad answer; it can be harmful. You need idempotency and rollback. **Observability** is non-negotiable: you must monitor each step the agent takes, which tool it called, what arguments, what the result was—because the failure could be at any step. **Testability** is hard because the agent's behaviour is probabilistic; you need tests that cover the possible paths. **Scalability** is about handling many agent sessions concurrently. **Maintainability** is about keeping the agents' prompts and tool definitions easy to change.

**Everyday applied ML (recommendation, vision, forecasting, fraud, speech).** These are the bread-and-butter ML systems. Here the ilities translate directly to production quality. **Maintainability** means the data pipeline and model code are clean enough that a new engineer can add a feature without two months of ramp-up. **Testability** means you have unit tests for the data cleaning, golden tests for the model, and integration tests for the whole pipeline. **Scalability** means the prediction service can handle peak traffic—you need horizontal scaling and load balancing. **Reliability** means you monitor up-time and have fallbacks if the model API fails. **Observability** means you log predictions, features, and outcomes so you can detect model drift.

**What a developer actually writes.** In practice, a developer writing a production ML service will use:

- A web framework like FastAPI or Flask for serving predictions. They write endpoints that accept a request, run the model, and return a response. For reliability, they add retries and timeouts.
- A logging library like `structlog` or `loguru` and a metrics library like `prometheus_client` for observability. They log every prediction, including the version of the model.
- A test framework like `pytest` with fixtures. They write tests that assert the model's output on known inputs.
- A caching layer like Redis for scalability—so repeated queries do not hit the model every time.
- A config management system (e.g., Hydra) for maintainability—so experiments are reproducible.

When any of this is done badly, the consequences are concrete: a system that fails at 2 AM with no logs to say why; a model that is fixed but cannot be updated because the code is tangled; a service that times out under a small load spike because it is not designed to scale. The ilities are the checklist that prevents these surprises.


---

Read this on the web: https://basavapurushottam.com/learn-ai/the-ilities
