# Single Responsibility

A module should have one reason to change, keeping its purpose narrow enough to state in a sentence.

**Leads to:** Separation of Concerns

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

*Software Engineering · Software Architecture*


## Why learn this

This is the first principle of writing software that does not collapse under its own weight. When you can see that a module has one job and one reason to change, you can split a tangled program into pieces you can name, test, and fix in isolation. That skill is the foundation for everything in your library: high cohesion and loose coupling, separation of concerns, layered architecture, hexagonal architecture, ports and adapters, and eventually the ability to sketch an entire system so that changing one part does not explode the rest. Without it, every later architectural pattern is just decoration on a messy pile.


## The intuition

Think of any tool you use every day. A kitchen knife cuts; it does not also stir, measure, or boil water. You reach for it because you know exactly what it does and what it does not do. A screwdriver drives screws; it does not hammer nails. Now imagine a single tool that is a knife, a spoon, a ruler, and a hammer all fused together. It might do all four jobs, but each one is awkward, and when one part breaks, you lose the whole tool. Software modules are the same. A module is a unit of code — a file, a class, a function — that has a name and a purpose. A well-designed module has one purpose, narrow enough that you can say it in one short sentence, like 'This module formats dates' or 'This module checks whether a user is allowed to view this page.' If you find yourself saying 'This module handles billing and sends emails and logs errors,' you have fused a knife with a hammer. The single responsibility principle is the rule that pushes you to keep those jobs separate. It is not about making code shorter or cleverer; it is about making change safe. When a requirement changes — and it always does — a module with one job changes for one reason only. If a module has three jobs, a change to one job risks breaking the other two. You want each change to touch as few places as possible, and each place to have one clear reason to exist.


## An analogy

Consider a small restaurant kitchen. There is a chef who cooks, a dishwasher who cleans plates, and a waiter who takes orders. Each person has one clear responsibility. Now imagine that the waiter also washes dishes when things are slow, the chef also greets customers at the door, and the dishwasher also handles the cash register. The restaurant becomes chaotic: a customer asks the chef about a reservation, and the chef burns the food; a dish breaks, and the dishwasher is handling cash, so nobody notices. When a problem happens, it is hard to tell who is responsible. The same chaos happens in code. A module that validates user input and also writes to a database and also formats an error message is a person doing three jobs. When a bug appears, you have to check three different concerns to find it. When you need to change the database library, you might accidentally break the input validation. The single responsibility principle says: one person, one job. The chef cooks. The waiter serves. The dishwasher cleans. In code, one module validates input. Another module writes to the database. Another formats error messages. Each can be changed, tested, and understood on its own. The analogy breaks down a little: in a restaurant, one person can do multiple jobs if they are skilled and the restaurant is small. That is true in code too — a tiny script can do several things and still be fine. The principle is about managing complexity. When the restaurant grows, the waitress who also cooks will become a bottleneck. When the codebase grows, a module that does two things will become a source of bugs. The further you go, the more the single responsibility principle pays off.


## Definition

A module should have one reason to change, meaning its purpose is narrow enough to state in a single sentence without using the word 'and' to connect different jobs.


## How it connects

You have not studied any other topics yet, so everything here is fresh ground. This principle is the seed. It connects directly to the 'Cohesion and Coupling' notes in your library — a module with one responsibility has high cohesion, because its contents are all about the same thing. It is the first step toward 'Separation of Concerns' and 'Layered Architecture', where different concerns live in different layers. When you later learn about 'Dependency Inversion' and 'Ports and Adapters', you will see that you cannot define a clean interface for a module that does five unrelated things. For now, hold onto this: every module you write should answer the question 'What do you do?' in one sentence.


## From scratch, in Python

```python
# We'll build a small program that reads a file of numbers, computes the average,
# and prints a report. Then we'll split it into single-responsibility modules.

# Step 1: The monolith (everything in one function)
def process_numbers(filename):
    # Read the file
    with open(filename) as f:
        lines = f.readlines()
    numbers = []
    for line in lines:
        numbers.append(int(line.strip()))

    # Compute the average
    total = 0
    count = 0
    for n in numbers:
        total += n
        count += 1
    average = total / count

    # Print a report
    print("Average: " + str(average))

# Call it
process_numbers("data.txt")

# Step 2: Split into single-responsibility functions
# Each function has one job and one reason to change.

def read_numbers(filename):
    """Reads a file of numbers, one per line, and returns a list of ints."""
    with open(filename) as f:
        lines = f.readlines()
    numbers = []
    for line in lines:
        numbers.append(int(line.strip()))
    return numbers

def compute_average(numbers):
    """Takes a list of numbers and returns the average."""
    total = 0
    count = 0
    for n in numbers:
        total += n
        count += 1
    return total / count

def print_report(average):
    """Prints the average in a human-readable format."""
    print("Average: " + str(average))

def main():
    numbers = read_numbers("data.txt")
    average = compute_average(numbers)
    print_report(average)

main()

# Now each function can be changed independently. For example, if we want to
# change the report format, we only change print_report. If we want to handle
# a different file format, we only change read_numbers.
```


## What the code does

First, look at the monolithic version. The function `process_numbers` does three things: it reads a file, it computes an average, and it prints a report. If you need to change how the report is formatted, you have to read through all the file-reading and averaging code to find the one print statement. If the file format changes, you touch the same function. Now look at the split version. `read_numbers` only reads a file and returns a list. `compute_average` only takes a list and returns a number. `print_report` only prints. `main` ties them together. Each function has one job. If you need to change how the average is calculated, you edit `compute_average` alone. If you need to change the report, you edit `print_report` alone. The other functions stay intact. This is the heart of the principle. Note that the split version is slightly longer — that is fine. The point is not fewer lines; it is separating reasons to change. You did not need to use lists, loops, or functions before, so here they are: a list is a variable that holds many values in order, like `[1, 2, 3]`. A loop repeats a block of code for each item in a list, as in `for n in numbers:`. A function is a named block of code that takes inputs and can return a value, like `compute_average`. These are the building blocks you just saw in action.


## Common misconceptions

**Single responsibility means a function can only be a few lines long** — A function has single responsibility if it is short, maybe less than 10 lines.

A function can be long because it does one complex job step by step. The length does not determine the number of responsibilities. A function that reads a file, parses it, and computes an average is long and does three jobs. A function that formats a complex report might be 50 lines and still do one job. In fact: Single responsibility is about having one reason to change. A long function that does only one thing is fine. A short function that does three things is not.

**A module has one responsibility if its name is a single word** — If I call a module 'Processor' or 'Manager', it probably has one job.

A vague name often hides multiple responsibilities. 'Processor' could mean anything. The name does not make the module single-responsibility; the content does. A module named 'Processor' that both reads files and sends emails has two jobs, regardless of its name. In fact: Test the responsibility by writing a sentence. If the sentence needs an 'and', it has more than one job.

**Single responsibility means a module cannot use other modules** — If my module calls another module, it is not single-responsibility.

Single responsibility is about a module's own purpose, not about whether it delegates. It is fine for a module to call other modules that have their own responsibilities. In fact, that is how systems are built. The key is that each module's own job is one thing. In fact: A module can call other modules freely as long as its own purpose is singular. The dependency is part of the system, not a violation.

**Single responsibility means one function per file** — Each file should contain exactly one function so that it has one responsibility.

A file can contain several functions that all help do the same job. For example, a file for date formatting might have a helper for parsing and another for formatting, both serving the same purpose. The file's responsibility is still one. In fact: The unit is a module, and a module can have many functions as long as they all serve one clear purpose. The test is whether the module as a whole has one reason to change.

**Single responsibility is about how lines of code are organized** — If I just move code into different files, I have satisfied single responsibility.

Moving code without changing its coupling does not help. If you split a function into three files but they all still read the same global variables and depend on each other tightly, they still have multiple responsibilities. The change must be logical, not just physical. In fact: Single responsibility is about logical separation — each module should cover one set of related behaviours. Physical file boundaries are only useful when they follow those logical lines.


## In short

A module should have one reason to change. If you can state its purpose in one sentence without an 'and', it is single-responsibility. This makes changes safer, testing easier, and code clearer. The principle is the first step toward all the architectural patterns in your library.


## Where this is used

In large language models like GPT-4, the principles of single responsibility appear at every level. The model itself is a giant neural network, but the software that serves it is divided into modules: one module handles tokenization — turning text into numbers; another runs the transformer layers — the core computation; another handles sampling — picking the next word; another manages the conversation history. Each module has one job. The tokenizer does not decide what word to say next; the sampler does not parse the prompt. When the tokenizer needs to support a new language, that change does not touch the transformer code. In small language models that run on a phone, the same separation is even more critical because memory is tight. Developers split the model into a small encoder module and a small decoder module, each with a single responsibility, so they can swap one without rewriting the other. In retrieval-augmented generation (RAG), the system has separate modules for embedding documents, retrieving relevant chunks, and generating answers. The retriever does not generate text; the generator does not search. When a developer changes the retrieval algorithm, the generation module remains unchanged. In agentic systems that plan and call tools, the agent might have a planner module, a tool-call module, and an execution module. The planner decides what to do; the tool-call module figures out which tool to invoke; the execution module actually runs the tool. If you want to add a new tool, you only touch the tool-call module. In everyday machine learning work — fraud detection, recommendation systems, image classification — models are trained and served as separate services. A fraud detection pipeline has a data ingestion module, a feature engineering module, a prediction module, and a notification module. Each has one job. A developer writing an API for a prediction model would use a framework like FastAPI and define a single endpoint that calls a prediction function. The endpoint does not also load the model; that is a separate module. When a change is needed — say, a new feature to include in the prediction — the developer edits the feature engineering module, not the API layer. If you build software that violates single responsibility, the breakage is obvious: a change to the way you log errors forces a recompile of the business logic. Debugging becomes a treasure hunt through a file that does five things. The principle is what makes testing possible: you can write a unit test for the `compute_average` function without any files or printing, because it is pure. That is why the principle is the door to maintainability, which your library lists as one of the 'ilities'.


---

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