CS 130


Welcome to Computer Science 130 - Software Engineering at UCLA

Design Patterns


Overview

In software engineering, a design pattern is a common, acceptable solution to a recurring design problem that arises within a specific context. The concept did not originate in computer science, but rather in architecture. Christopher Alexander, an architect who pioneered the idea, defined a pattern beautifully: “Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice”.

In software development, design patterns refer to medium-level abstractions that describe structural and behavioral aspects of software. They sit between low-level language idioms (like how to efficiently concatenate strings in Java) and large-scale architectural patterns (like Model-View-Controller or client-server patterns). Structurally, they deal with classes, objects, and the assignment of responsibilities; behaviorally, they govern method calls, message sequences, and execution semantics.

Anatomy of a Pattern

A true pattern is more than simply a good idea or a random solution; it requires a structured format to capture the problem, the context, the solution, and the consequences. While various authors use slightly different templates, the fundamental anatomy of a design pattern contains the following essential elements:

  • Pattern Name: A good name is vital as it becomes a handle we can use to describe a design problem, its solution, and its consequences in a word or two. Naming a pattern increases our design vocabulary, allowing us to design and communicate at a higher level of abstraction.
  • Context: This defines the recurring situation or environment in which the pattern applies and where the problem exists.
  • Problem: This describes the specific design issue or goal you are trying to achieve, along with the constraints symptomatic of an inflexible design.
  • Forces: This outlines the trade-offs and competing concerns that must be balanced by the solution.
  • Solution: This describes the elements that make up the design, their relationships, responsibilities, and collaborations. It specifies the spatial configuration and behavioral dynamics of the participating classes and objects.
  • Consequences: This explicitly lists the results, costs, and benefits of applying the pattern, including its impact on system flexibility, extensibility, portability, performance, and other quality attributes.

GoF Design Patterns

Here are some examples of design patterns that we describe in more detail:

  • State: Encapsulates state-based behavior into distinct classes, allowing a context object to dynamically alter its behavior at runtime by delegating operations to its current state object.

  • Observer: Establishes a one-to-many dependency between objects, ensuring that a group of dependent objects is automatically notified and updated whenever the internal state of their shared subject changes.

Architectural Patterns

Here are some examples of architectural patterns that we describe in more detail:

  • Model-View-Controller (MVC): The Model-View-Controller (MVC) architectural pattern decomposes an interactive application into three distinct components: a model that encapsulates the core application data and business logic, a view that renders this information to the user, and a controller that translates user inputs into corresponding state updates

The Benefits of a Shared Toolbox

Just as a mechanic must know their toolbox, a software engineer must know design patterns intimately—understanding their advantages, disadvantages, and knowing precisely when (and when not) to use them.

  • A Common Language for Communication: The primary challenge in multi-person software development is communication. Patterns solve this by providing a robust, shared vocabulary. If an engineer suggests using the “Observer” or “Strategy” pattern, the team instantly understands the problem, the proposed architecture, and the resulting interactions without needing a lengthy explanation.
  • Capturing Design Intent: When you encounter a design pattern in existing code, it communicates not only what the software does, but why it was designed that way.
  • Reusable Experience: Patterns are abstractions of design experience gathered by seasoned practitioners. By studying them, developers can rely on tried-and-tested methods to build flexible and maintainable systems instead of reinventing the wheel.

Challenges and Pitfalls of Design Patterns

Despite their power, design patterns are not silver bullets. Misusing them introduces severe challenges:

  • The “Hammer and Nail” Syndrome: Novice developers who just learned patterns often try to apply them to every problem they see. Software quality is not measured by the number of patterns used. Often, keeping the code simple and avoiding a pattern entirely is the best solution.
  • Over-engineering vs. Under-engineering: Under-engineering makes software too rigid for future changes. However, over-applying patterns leads to over-engineering—creating premature abstractions that make the codebase unnecessarily complex, unreadable, and a waste of development time. Developers must constantly balance simplicity (fewer classes and patterns) against changeability (greater flexibility but more abstraction).
  • Implicit Dependencies: Patterns intentionally replace static, compile-time dependencies with dynamic, runtime interactions. This flexibility comes at a cost: it becomes harder to trace the execution flow and state of the system just by reading the code.
  • Misinterpretation as Recipes: A pattern is an abstract idea, not a snippet of code from Stack Overflow. Integrating a pattern into a system is a human-intensive, manual activity that requires tailoring the solution to fit a concrete context.

Context Tailoring

It is important to remember that the standard description of a pattern presents an abstract solution to an abstract problem. Integrating a pattern into a software system is a highly human-intensive, manual activity; patterns cannot simply be misinterpreted as step-by-step recipes or copied as raw code. Instead, developers must engage in context tailoring—the process of taking an abstract pattern and instantiating it into a concrete solution that perfectly fits the concrete problem and the concrete context of their application.

Because applying a pattern outside of its intended problem space can result in bad design (such as the notorious over-use of the Singleton pattern), tailoring ensures that the pattern acts as an effective tool rather than an arbitrary constraint.

The Tailoring Process: The Measuring Tape and the Scissors

Context tailoring can be understood through the metaphor of making a custom garment, which requires two primary steps: using a “measuring tape” to observe the context, and using “scissors” to make the necessary adjustments.

1. Observation of Context

Before altering a design pattern, you must thoroughly observe and measure the environment in which it will operate. This involves analyzing three main areas:

  • Project-Specific Needs: What kind of evolution is expected? What features are planned for the future, and what frameworks is the system currently relying on?
  • Desired System Properties: What are the overarching goals of the software? Must the architecture prioritize run-time performance, strict security, or long-term maintainability?
  • The Periphery: What is the complexity of the surrounding environment? Which specific classes, objects, and methods will directly interact with the pattern’s participants?

2. Making Adjustments

Once the context is mapped, developers must “cut” the pattern to fit. This requires considering the broad design space of the pattern and exploring its various alternatives and variation points. After evaluating the context-specific consequences of these potential variations, the developer implements the most suitable version. Crucially, the design decisions and the rationale behind those adjustments must be thoroughly documented. Without documentation, future developers will struggle to understand why a pattern deviates from its textbook structure.

Dimensions of Variation

Every design pattern describes a broad design space containing many distinct variations. When tailoring a pattern, developers typically modify it along four primary dimensions:

Structural Variations

These variations alter the roles and responsibility assignments defined in the abstract pattern, directly impacting how the system can evolve. For example, the Factory Method pattern can be structurally varied by removing the abstract product class entirely. Instead, a single concrete product is implemented and configured with different parameters. This variation trades the extensibility of a massive subclass hierarchy for immediate simplicity.

Behavioral Variations

Behavioral variations modify the interactions and communication flows between objects. These changes heavily impact object responsibilities, system evolution, and run-time quality attributes like performance. A classic example is the Observer pattern, which can be tailored into a “Push model” (where the subject pushes all updated data directly to the observer) or a “Pull model” (where the subject simply notifies the observer, and the observer must pull the specific data it needs).

Internal Variations

These variations involve refining the internal workings of the pattern’s participants without necessarily changing their external structural interfaces. A developer might tailor a pattern internally by choosing a specific list data structure to hold observers, adding thread-safety mechanisms, or implementing a specialized sorting algorithm to maximize performance for expected data sets.

Language-Dependent Variations

Modern programming languages offer specific constructs that can drastically simplify pattern implementations. For instance, dynamically typed languages can often omit explicit interfaces, and aspect-oriented languages can replace standard polymorphism with aspects and point-cuts. However, there is a dangerous trap here: using language features to make a pattern entirely reusable as code (e.g., using include Singleton in Ruby) eliminates the potential for context tailoring. Design patterns are fundamentally about design reuse, not exact code reuse.

The Global vs. Local Optimum Trade-off

While context tailoring is essential, it introduces a significant challenge in large-scale software projects. Perfectly tailoring a pattern to every individual sub-problem creates a “local optimum”. However, a large amount of pattern variation scattered throughout a single project can lead to severe confusion due to overloaded meaning.

If developers use the textbook Observer pattern in one module, but highly customized, structurally varied Observers in another, incoming developers might falsely assume identical behavior simply because the classes share the “Observer” naming convention. To mitigate this, large teams must rely on project conventions to establish pattern consistency. Teams must explicitly decide whether to embrace diverse, highly tailored implementations (and name them distinctly) or to enforce strict guidelines on which specific pattern variants are permitted within the codebase.

Pattern Compounds

In software design, applying individual design patterns is akin to utilizing distinct compositional techniques in photography—such as symmetry, color contrast, leading lines, and a focal object. Simply having these patterns present does not guarantee a masterpiece; their deliberate arrangement is crucial. When leading lines intentionally point toward a focal object, a more pleasing image emerges. In software architecture, this synergistic combination is known as a pattern compound.

A pattern compound is a reoccurring set of patterns with overlapping roles from which additional properties emerge. Notably, pattern compounds are patterns in their own right, complete with an abstract problem, an abstract context, and an abstract solution. While pattern languages provide a meta-level conceptual framework or grammar for how patterns relate to one another, pattern compounds are concrete structural and behavioral unifications.

The Anatomy of Pattern Compounds

The core characteristic of a pattern compound is that the participating domain classes take on multiple superimposed roles simultaneously. By explicitly connecting patterns, developers can leverage one pattern to solve a problem created by another, leading to a new set of emergent properties and consequences.

Solving Structural Complexity: The Composite Builder

The Composite pattern is excellent for creating unified tree structures, but initializing and assembling this abstract object structure is notoriously difficult. The Builder pattern, conversely, is designed to construct complex object structures. By combining them, the Composite’s Component acts as the Builder’s AbstractProduct, while the Leaf and Composite act as ConcreteProducts.

This compound yields the emergent properties of looser coupling between the client and the composite structure and the ability to create different representations of the encapsulated composite. However, as a trade-off, dealing with a recursive data structure within a Builder introduces even more complexity than using either pattern individually.

Managing Operations: The Composite Visitor and Composite Command

Pattern compounds frequently emerge when scaling behavioral patterns to handle structural complexity:

  • Composite Visitor: If a system requires many custom operations to be defined on a Composite structure without modifying the classes themselves (and no new leaves are expected), a Visitor can be superimposed. This yields the emergent property of strict separation of concerns, keeping core structural elements distinct from use-case-specific operations.
  • Composite Command: When a system involves hierarchical actions that require a simple execution API, a Composite Command groups multiple command objects into a unified tree. This allows individual command pieces to be shared and reused, though developers must manage the consequence of execution order ambiguity.

Communicating Design Intent and Context Tailoring

Pattern compounds also naturally arise when tailoring patterns to specific contexts or when communicating highly specific design intents.

  • Null State / Null Strategy: If an object enters a “do nothing” state, combining the State pattern with the Null Object pattern perfectly communicates the design intent of empty behavior. (Note that there is no Null Decorator, as a decorator must fully implement the interface of the decorated object).
  • Singleton State: If State objects are entirely stateless—meaning they carry behavior but no data, and do not require a reference back to their Context—they can be implemented as Singletons. This tailoring decision saves memory and eases object creation, though it permanently couples the design by removing the ability to reference the Context in the future.

The Advantages of Compounding Patterns

The primary advantage of pattern compounds is that they make software design more coherent. Instead of finding highly optimized but fragmented patchwork solutions for every individual localized problem, compounds provide overarching design ideas and unifying themes. They raise the composition of patterns to a higher semantic abstraction, enabling developers to systematically foresee how the consequences of one pattern map directly to the context of another.

Challenges and Pitfalls

Despite their power, pattern compounds introduce distinct architectural and cognitive challenges:

  • Mixed Concerns: Because pattern compounds superimpose overlapping roles, a single class might juggle three distinct concerns: its core domain functionality, its responsibility in the first pattern, and its responsibility in the second. This can severely overload a class and muddle its primary responsibility.
  • Obscured Foundations: Tightly compounding patterns can make it much harder for incoming developers to visually identify the individual, foundational patterns at play.
  • Naming Limitations: Accurately naming a class to reflect its domain purpose alongside multiple pattern roles (e.g., a “PlayerObserver”) quickly becomes unmanageable, forcing teams to rely heavily on external documentation to explain the architecture.
  • The Over-Engineering Trap: As with any design abstraction, possessing the “hammer” of a pattern compound does not make every problem a nail. Developers must constantly evaluate whether the resulting architectural complexity is truly justified by the context.

Advanced Concepts

Patterns Within Patterns: Core Principles

When analyzing various design patterns, you will begin to notice recurring micro-architectures. Design patterns are often built upon fundamental software engineering principles:

  • Delegation over Inheritance: Subclassing can lead to rigid designs and code duplication (e.g., trying to create an inheritance tree for cars that can be electric, gas, hybrid, and also either drive or fly). Patterns like Strategy, State, and Bridge solve this by extracting varying behaviors into separate classes and delegating responsibilities to them.
  • Polymorphism over Conditions: Patterns frequently replace complex if/else or switch statements with polymorphic objects. For instance, instead of conditional logic checking the state of an algorithm, the Strategy pattern uses interchangeable objects to represent different execution paths.
  • Additional Layers of Indirection: To reduce strong coupling between interacting components, patterns like the Mediator or Facade introduce an intermediate object to handle communication. While this centralizes logic and improves changeability, it can create long traces of method calls that are harder to debug.

Domain-Specific and Application-Specific Patterns

The Gang of Four patterns are generic to object-oriented programming, but patterns exist at all levels.

  • Domain-Specific Patterns: Certain industries (like Game Development, Android Apps, or Security) have their own highly tailored patterns. Because these patterns make assumptions about a specific domain, they generally carry fewer negative consequences within their niche, but they require the team to actually possess domain expertise.
  • Application-Specific Patterns: Every distinct software project will eventually develop its own localized patterns—agreed-upon conventions and structures unique to that team. Identifying and documenting these implicit patterns is one of the most critical steps when a new developer joins an existing codebase, as it massively improves program comprehension.

Conclusion

Design patterns are the foundational building blocks of robust software architecture. However, they are a substitute for neither domain expertise nor critical thought. The mark of an expert engineer is not knowing how to implement every pattern, but possessing the wisdom to evaluate trade-offs, carefully observe the context, and know exactly when the simplest code is actually the smartest design.

Observer


Problem 

In software design, you frequently encounter situations where one object’s state changes, and several other objects need to be notified of this change so they can update themselves accordingly.

If the dependent objects constantly check the core object for changes (polling), it wastes valuable CPU cycles and resources. Conversely, if the core object is hard-coded to directly update all its dependent objects, the classes become tightly coupled. Every time you need to add or remove a dependent object, you have to modify the core object’s code, violating the Open/Closed Principle.

The core problem is: How can a one-to-many dependency between objects be maintained efficiently without making the objects tightly coupled?

Context

The Observer pattern is highly applicable in scenarios requiring distributed event handling systems or highly decoupled architectures. Common contexts include:

  • User Interfaces (GUI): A classic example is the Model-View-Controller (MVC) architecture. When the underlying data (Model) changes, multiple UI components (Views) like charts, tables, or text fields must update simultaneously to reflect the new data.

  • Event Management Systems: Applications that rely on events—such as user button clicks, incoming network requests, or file system changes—where an unknown number of listeners might want to react to a single event.

  • Social Media/News Feeds: A system where users (observers) follow a specific creator (subject) and need to be notified instantly when new content is posted.

Solution

The Observer design pattern solves this by establishing a one-to-many subscription mechanism.

It introduces two main roles: the Subject (the object sending updates after it has changed) and the Observer (the object listening to the updates of Subjects).

Instead of objects polling the Subject or the Subject being hard-wired to specific objects, the Subject maintains a dynamic list of Observers. It provides an interface for Observers to attach and detach themselves at runtime. When the Subject’s state changes, it iterates through its list of attached Observers and calls a specific notification method (e.g., update()) defined in the Observer interface.

This creates a loosely coupled system: the Subject only knows that its Observers implement a specific interface, not their concrete implementation details.

Design Decisions

Push vs. Pull Model:

Push Model: The Subject sends the detailed state information to the Observer as arguments in the update() method, even if the Observer doesn’t need all data. This keeps the Observer completely decoupled from the Subject but can be inefficient if large data is passed unnecessarily.

Pull Model: The Subject sends a minimal notification, and the Observer is responsible for querying the Subject for the specific data it needs. This requires the Observer to have a reference back to the Subject, slightly increasing coupling, but it is often more efficient.

Factory Method


Context

In software construction, we often find ourselves in situations where a “Creator” class needs to manage a lifecycle of actions—such as preparing, processing, and delivering an item—but the specific type of item it handles varies based on the environment.

For example, imagine a PizzaStore that needs to orderPizza(). The store follows a standard process: it must prepare(), bake(), cut(), and box() the pizza. However, the specific type of pizza (New York style vs. Chicago style) depends on the store’s physical location. The “Context” here is a system where the high-level process is stable, but the specific objects being acted upon are volatile and vary based on concrete subclasses.

Problem

Without a creational pattern, developers often resort to “Big Upfront Logic” using complex conditional statements. You might see code like this:

public Pizza orderPizza(String type) {
    Pizza pizza;
    if (type.equals("cheese")) { pizza = new CheesePizza(); }
    else if (type.equals("greek")) { pizza = new GreekPizza(); }
    // ... more if-else blocks ...
    pizza.prepare();
    pizza.bake();
    return pizza;
}

This approach presents several critical challenges:

  1. Violation of Single Responsibility Principle: This single method is now responsible for both deciding which pizza to create and managing the baking process.
  2. Divergent Change: Every time the menu changes or the baking process is tweaked, this method must be modified, making it a “hot spot” for bugs.
  3. Tight Coupling: The store is “intimately” aware of every concrete pizza class, making it impossible to add new regional styles without rewriting the store’s core logic.

Solution

The Factory Method Pattern solves this by defining an interface for creating an object but letting subclasses decide which class to instantiate. It effectively “defers” the responsibility of creation to subclasses.

In our PizzaStore example, we make the createPizza() method abstract within the base PizzaStore class. This abstract method is the “Factory Method”. We then create concrete subclasses like NYPizzaStore and ChicagoPizzaStore, each implementing createPizza() to return their specific regional variants.

The structure involves four key roles:

  • Product: The common interface for the objects being created (e.g., Pizza).
  • Concrete Product: The specific implementation (e.g., NYStyleCheesePizza).
  • Creator: The abstract class that contains the high-level business logic (the “Template Method”) and declares the Factory Method.
  • Concrete Creator: The subclass that implements the Factory Method to produce the actual product.

Consequences

The primary benefit of this pattern is decoupling: the high-level “Creator” code is completely oblivious to which “Concrete Product” it is actually using. This allows the system to evolve independently; you can add a LAPizzaStore without touching a single line of code in the original PizzaStore base class.

However, there are trade-offs:

  • Boilerplate Code: It requires creating many new classes (one for each product type and one for each creator type), which can increase the “static” complexity of the code.
  • Program Comprehension: While it reduces long-term maintenance costs, it can make the initial learning curve steeper for new developers who aren’t familiar with the pattern.

Abstract Factory


Context

In complex software systems, we often encounter situations where we must manage multiple categories of related objects that need to work together consistently. Imagine a software framework for a pizza franchise that has expanded into different regions, such as New York and Chicago. Each region has its own specific set of ingredients: New York uses thin crust dough and Marinara sauce, while Chicago uses thick crust dough and plum tomato sauce. The high-level process of preparing a pizza remains stable across all locations, but the specific “family” of ingredients used depends entirely on the geographical context.

Problem

The primary challenge arises when a system needs to be independent of how its products are created, but those products belong to families that must be used together. Without a formal creational pattern, developers might encounter the following issues:

  • Inconsistent Product Groupings: There is a risk that a “rogue” franchise might accidentally mix New York thin crust with Chicago deep-dish sauce, leading to a product that doesn’t meet quality standards.
  • Parallel Inheritance Hierarchies: You often end up with multiple hierarchies (e.g., a Dough hierarchy, a Sauce hierarchy, and a Cheese hierarchy) that all need to be instantiated based on the same single decision point, such as the region.
  • Tight Coupling: If the Pizza class directly instantiates concrete ingredient classes, it becomes “intimate” with every regional variation, making it incredibly difficult to add a new region like Los Angeles without modifying existing code.

Solution

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It essentially acts as a “factory of factories,” or more accurately, a single factory that contains multiple Factory Methods.

The design pattern involves these roles:

  1. Abstract Factory Interface: Defining an interface (e.g., PizzaIngredientFactory) with a creation method for each type of product in the family (e.g., createDough(), createSauce()).
  2. Concrete Factories: Implementing regional subclasses (e.g., NYPizzaIngredientFactory) that produce the specific variants of those products.
  3. Client: The client (e.g., the Pizza class) no longer knows about specific ingredients. Instead, it is passed an IngredientFactory and simply asks for its components, remaining completely oblivious to whether it is receiving New York or Chicago variants.

Consequences

Applying the Abstract Factory pattern results in several significant architectural trade-offs:

  • Isolation of Concrete Classes: It decouples the client code from the actual factory and product implementations, promoting high information hiding.
  • Promoting Consistency: It ensures that products from the same family are always used together, preventing incompatible combinations.
  • Ease of Adding New Families: Adding a new look-and-feel or a new region is a “pure addition”—you simply create a new concrete factory and new product implementations without touching existing code.
  • The “Rigid Interface” Drawback: While adding new families is easy, adding new types of products to the family is difficult. If you want to add “Pepperoni” to your ingredient family, you must change the Abstract Factory interface and modify every single concrete factory subclass to implement the new method.

Adapter


Context

In software construction, we frequently encounter situations where an existing system needs to collaborate with a third-party library, a vendor class, or legacy code. However, these external components often have interfaces that do not match the specific “Target” interface our system was designed to use.

A classic real-world analogy is the power outlet adapter. If you take a US laptop to London, the laptop’s plug (the client) expects a US power interface, but the wall outlet (the adaptee) provides a European interface. To make them work together, you need an adapter that translates the interface of the wall outlet into one the laptop can plug into. In software, the Adapter pattern acts as this “middleman”, allowing classes to work together that otherwise couldn’t due to incompatible interfaces.

Problem

The primary challenge occurs when we want to use an existing class, but its interface does not match the one we need. This typically happens for several reasons:

  • Legacy Code: We have code written a long time ago that we don’t want to (or can’t) change, but it must fit into a new, more modern architecture.
  • Vendor Lock-in: We are using a vendor class that we cannot modify, yet its method names or parameters don’t align with our system’s requirements.
  • Syntactic and Semantic Mismatches: Two interfaces might differ in syntax (e.g., getDistance() in inches vs. getLength() in meters) or semantics (e.g., a method that performs a similar action but with different side effects).

Without an adapter, we would be forced to rewrite our existing system code to accommodate every new vendor or legacy class, which violates the Open/Closed Principle and creates tight coupling.

Solution

The Adapter Pattern solves this by creating a class that converts the interface of an “Adaptee” class into the “Target” interface that the “Client” expects.

According to the course material, there are four key roles in this structure:

  1. Target: The interface the Client wants to use (e.g., a Duck interface with quack() and fly()).
  2. Adaptee: The existing class with the incompatible interface that needs adapting (e.g., a WildTurkey class that gobble()s instead of quack()s).
  3. Adapter: The class that realizes the Target interface while holding a reference to an instance of the Adaptee.
  4. Client: The class that interacts only with the Target interface, remaining completely oblivious to the fact that it is actually communicating with an Adaptee through the Adapter.

In the “Turkey that wants to be a Duck” example, we create a TurkeyAdapter that implements the Duck interface. When the client calls quack() on the adapter, the adapter internally calls gobble() on the wrapped turkey object. This syntactic translation effectively hides the underlying implementation from the client.

Consequences

Applying the Adapter pattern results in several significant architectural trade-offs:

  • Loose Coupling: It decouples the client from the legacy or vendor code. The client only knows the Target interface, allowing the Adaptee to evolve independently without breaking the client code.
  • Information Hiding: It follows the Information Hiding principle by concealing the “secret” that the system is using a legacy component.
  • Single vs. Multiple Adapters: In languages like Java, we typically use “Object Adapters” via composition (wrapping the adaptee). In languages like C++, “Class Adapters” can be created using multiple inheritance to inherit from both the Target and the Adaptee.
  • Flexibility vs. Complexity: While adapters make a system more flexible, they add a layer of indirection that can make it harder to trace the execution flow of the program since the client doesn’t know which object is actually receiving the signal.

Singleton


Context

In software engineering, certain classes represent concepts that should only exist once during the entire execution of a program. Common examples include thread pools, caches, dialog boxes, logging objects, and device drivers. In these scenarios, having more than one instance is not just unnecessary but often harmful to the system’s integrity. In a UML class diagram, this requirement is explicitly modeled by specifying a multiplicity of “1” in the upper right corner of the class box, indicating the class is intended to be a singleton.

Problem

The primary problem arises when instantiating more than one of these unique objects leads to incorrect program behavior, resource overuse, or inconsistent results. For instance, accidentally creating two distinct “Earth” objects in a planetary simulation would break the logic of the system.

While developers might be tempted to use global variables to manage these unique objects, this approach introduces several critical flaws:

  • High Coupling: Global variables allow any part of the system to access and potentially mess around with the object, creating a web of dependencies that makes the code hard to maintain.
  • Lack of Control: Global variables do not prevent a developer from accidentally calling the constructor multiple times to create a second, distinct instance.
  • Instantiation Issues: You may want the flexibility to choose between “eager instantiation” (creating the object at program start) or “lazy instantiation” (creating it only when first requested), which simple global variables do not inherently support.

Solution

The Singleton Pattern solves these issues by ensuring a class has only one instance while providing a controlled, global point of access to it. The solution consists of three main implementation aspects:

  1. A Private Constructor: By declaring the constructor private, the pattern prevents external classes from ever using the new keyword to create an instance.
  2. A Static Field: The class maintains a private static variable (often named uniqueInstance) to hold its own single instance.
  3. A Static Access Method: A public static method, typically named getInstance(), serves as the sole gateway to the object.

Refining the Solution: Thread Safety and Performance

The “Classic Singleton” implementation uses lazy instantiation, checking if the instance is null before creating it. However, this is not thread-safe; if two threads call getInstance() simultaneously, they might both find the instance to be null and create two separate objects.

There are several ways to handle this in Java:

  • Synchronized Method: Adding the synchronized keyword to getInstance() makes the operation atomic but introduces significant performance overhead, as every call to get the instance is forced to wait in a queue, even after the object has already been created.
  • Eager Instantiation: Creating the instance immediately when the class is loaded avoids thread issues entirely but wastes memory if the object is never actually used during execution.
  • Double-Checked Locking: This advanced approach uses the volatile keyword on the instance field to ensure it is handled correctly across threads. It checks for a null instance twice—once before entering a synchronized block and once after—minimizing the performance hit of synchronization to only the very first time the object is created.

Consequences

Applying the Singleton Pattern results in several important architectural outcomes:

  • Controlled Access: The pattern provides a single point of access that can be easily managed and updated.
  • Resource Efficiency: It prevents the system from being cluttered with redundant, resource-intensive objects.
  • The Risk of “Singleitis”: A major drawback is the tendency for developers to overuse the pattern. Using a Singleton just for easy global access can lead to a hard-to-maintain design with high coupling, where it becomes unclear which classes depend on the Singleton and why.
  • Complexity in Testing: Singletons can be difficult to mock during unit testing because they maintain state throughout the lifespan of the application.

Mediator


Context

In complex software systems, we often encounter a “family” of objects that must work together to achieve a high-level goal. A classic scenario is Bob’s Java-enabled smart home. In this system, various appliances like an alarm clock, a coffee maker, a calendar, and a garden sprinkler must coordinate their behaviors. For instance, when the alarm goes off, the coffee maker should start brewing, but only if it is a weekday according to the calendar.

Problem

When these objects communicate directly, several architectural challenges arise:

  • Many-to-Many Complexity: As the number of objects grows, the number of direct inter-communications increases exponentially (N*N), leading to a tangled web of dependencies.
  • Low Reusability: Because the coffee pot must “know” about the alarm clock and the calendar to function within Bob’s specific rules, it becomes impossible to reuse that coffee pot code in a different home that lacks a sprinkler or a specialized calendar.
  • Scattered Logic: The “rules” of the system (e.g., “no coffee on weekends”) are spread across multiple classes, making it difficult to find where to make changes when those rules evolve.
  • Inappropriate Intimacy: Objects spend too much time delving into each other’s private data or specific method names just to coordinate a simple task.

Solution

The Mediator Pattern solves this by encapsulating many-to-many communication dependencies within a single “Mediator” object. Instead of objects talking to each other directly, they only communicate with the Mediator.

The objects (often called “colleagues”) tell the Mediator when their state changes. The Mediator then contains all the complex control logic and coordination rules to tell the other objects how to respond. For example, the alarm clock simply tells the Mediator “I’ve been snoozed,” and the Mediator checks the calendar and decides whether to trigger the coffee maker. This reduces the communication structure from N-to-N complex dependencies to a simpler N-to-1 structure.

Consequences

Applying the Mediator pattern involves significant trade-offs:

  • Increased Reusability: Individual objects become more reusable because they make fewer assumptions about the existence of other objects or specific system requirements.
  • Simplified Maintenance: Control logic is localized in one component, making it easy to find and update rules without touching the colleague classes.
  • Extensibility vs. Changeability: While patterns like Observer are great for adding new types of objects (extensibility), the Mediator is specifically designed for changing existing behaviors and interactions (changeability).
  • The “God Class” Risk: A major drawback is that, without careful design, the Mediator itself can become an overly complex “god class” or a “drunk junk drawer” that is impossible to maintain.
  • Single Point of Failure: Because all communication flows through one object, the Mediator represents a single point of failure and a potential security vulnerability.
  • Complexity Displacement: It is important to note that the Mediator does not actually remove the inherent complexity of the interactions; it simply provides a structure for centralizing it.

Facade


Context

In modern software construction, we often build systems composed of multiple complex subsystems that must collaborate to perform a high-level task. A classic example is a Home Theater System. This system consists of various independent components: an amplifier, a DVD player, a projector, a motorized screen, theater lights, and even a popcorn popper. While each of these components is a powerful “module” on its own, they must be coordinated precisely to provide a seamless user experience.

Problem

When a client needs to interact with a set of complex subsystems, several issues arise:

  1. High Complexity: To perform a single logical action like “Watch a Movie,” the client might have to execute a long sequence of manual steps—turning on the popper, dimming lights, lowering the screen, configuring the projector input, and finally starting the DVD player.
  2. Maintenance Nightmares: If the movie finishes, the user has to perform all those steps again in reverse order. If a component is upgraded (e.g., replacing a DVD player with a streaming device), every client that uses the system must learn a new, slightly different procedure.
  3. Tight Coupling: The client code becomes “intimate” with every single class in the subsystem. This violates the principle of Information Hiding, as the client must understand the internal low-level details of how each device operates just to use the system.

Solution

The Façade Pattern provides a unified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use by wrapping complexity behind a single, simplified object.

In the Home Theater example, we create a HomeTheaterFacade. Instead of the client calling twelve different methods on six different objects, the client calls one high-level method: watchMovie(). The Façade object then handles the “dirty work” of delegating those requests to the underlying subsystems. This creates a single point of use for the entire component, effectively hiding the complex “how” of the implementation from the outside world.

Consequences

Applying the Façade pattern leads to several architectural benefits and trade-offs:

  • Simplified Interface: The primary intent of a Façade is to simplify the interface for the client.
  • Reduced Coupling: It decouples the client from the subsystem. Because the client only interacts with the Façade, internal changes to the subsystem (like adding a new device) do not require changes to the client code.
  • Improved Information Hiding: It promotes modularity by ensuring that the low-level details of the subsystems are “secrets” kept within the component.
  • Façade vs. Adapter: It is important to distinguish this from the Adapter Pattern. While an Adapter’s intent is to convert one interface into another to match a client’s expectations, a Façade’s intent is solely to simplify a complex set of interfaces.
  • Flexibility: Clients that still need the power of the low-level interfaces can still access them directly; the Façade does not “trap” the subsystem, it just provides a more convenient way to use it for common tasks.

Design Principles


Information Hiding

Description

SOLID

Description

Information Hiding


In the realm of software engineering, few principles are as foundational or as frequently misunderstood as Information Hiding (IH). While often confused with simply making variables “private,” IH is a sophisticated strategy for managing the overwhelming complexity inherent in modern software systems.

Historical Context

To understand why we hide information, we must look back to the mid-1960s. During the Apollo missions, lead software engineer Margaret Hamilton noted that software complexity had already surpassed hardware complexity. By 1968, the industry reached a “Software Crisis” where projects were consistently over budget, behind schedule, and failing to meet specifications. In response, David Parnas published a landmark paper in 1972 proposing a new way to decompose systems. He argued that instead of breaking a program into steps (like a flowchart), engineers should identify “difficult design decisions” or “decisions likely to change” and encapsulate each one within its own module.

The Core Principle: Secrets and Interfaces

The Information Hiding principle states that design decisions likely to change independently should be the “secrets” of separate modules. A module is defined as an independent work unit—such as a function, class, directory, or library—that can be assigned to a single developer. Every module consists of two parts:

  • The Interface (API): A stable contract that describes what the module does. It should only reveal assumptions that are unlikely to change.
  • The Implementation: The “secret” code that describes how the module fulfills its contract. This part can be changed freely without affecting the rest of the system, provided the interface remains the same.

A classic real-world example is the power outlet. The interface is the standard two or three-prong socket. As a user, you do not need to know if the power is generated by solar, wind, or nuclear energy; you only care that it provides electricity. This allows the “implementation” (the power source) to change without requiring you to replace your appliances.

Common “Secrets” to Hide

Successful modularization requires identifying which details are volatile. Common secrets include:

  • Data Structures: Whether data is stored in an array, a linked list, or a hash map.
  • Data Storage: Whether information is stored on a local disk, in a SQL database, or in the cloud.
  • Algorithms: The specific steps of a computation, such as using A* versus Dijkstra for pathfinding.
  • External Dependencies: The specific libraries or frameworks used, such as choosing between Axios or Fetch for network requests.

Software Process


Agile

For decades, software development was dominated by the Waterfall model, a sequential process where each phase—requirements, design, implementation, verification, and maintenance—had to be completed entirely before the next began. This “Big Upfront Design” approach assumed that requirements were stable and that designers could predict every challenge before a single line of code was written. However, this led to significant industry frustrations: projects were frequently delayed, and because customer feedback arrived only at the very end of the multi-year cycle, teams often delivered products that no longer met the user’s changing needs.

Agile Manifesto

In 2001, a group of software experts met in Utah to address these failures, resulting in the Agile Manifesto. Rather than a rigid rulebook, the manifesto proposed a shift in values:

  • Individuals and interactions over processes and tools
  • Working software over comprehensive documentation
  • Customer collaboration over contract negotiation
  • Responding to change over following a plan While the authors acknowledged value in the items on the right, they insisted that the items on the left were more critical for success in complex environments.

Core Principles

The heart of Agility lies in iterative and incremental development. Instead of one long cycle, work is broken into short, time-boxed periods—often called Sprints—typically lasting one to four weeks. At the end of each sprint, the team delivers a “Working Increment” of the product, which is demonstrated to the customer to gather rapid feedback. This ensures the team is always building the “right” system and can pivot if requirements evolve. Key principles supporting this include:

  • Customer Satisfaction: Delivering valuable software early and continuously.
  • Simplicity: The art of maximizing the amount of work not done.
  • Technical Excellence: Continuous attention to good design to enhance long-term agility.
  • Self-Organizing Teams: Empowering developers to decide how to best organize their own work rather than acting as “coding monkeys”.

Common Agile Processes

The most common agile processes include:

  • Scrum: The most popular framework using roles like Scrum Master, Product Owner, and Developers.
  • Extreme Programming (XP): Focused on technical excellence through “extreme” versions of good practices, such as Test-Driven Development (TDD), Pair Programming, Continuous Integration, and Collective Code Ownership
  • Lean Software Development: Derived from Toyota’s manufacturing principles, Lean focuses on eliminating waste

Scrum


0:00
--:--

While many organizations claim to be “Agile”, the vast majority (roughly 63%) implement the Scrum framework.

Scrum Theory

Scrum is a management framework built on the philosophy of Empiricism. This philosophy asserts that in complex environments like software development, we cannot rely on detailed upfront predictions. Instead, knowledge comes from experience, and decisions must be based on what is actually observed and measured in a “real” product.

To make empiricism actionable, Scrum rests on three core pillars:

  • Transparency: Significant aspects of the process must be visible to everyone responsible for the outcome. “The work is on the wall”, meaning stakeholders and developers alike should see exactly where the project stands via artifacts like Kanban boards.
  • Inspection: The team must frequently and diligently check their progress toward the Sprint Goal to detect undesirable variances.
  • Adaptation: If inspection reveals that the process or product is unacceptable, the team must adjust immediately to minimize further issues. It is important to realize that Scrum is not a fixed process but one designed to be tailored to a team’s specific domain and needs.

Scrum Roles

0:00
--:--

Scrum defines three specific roles that are intentionally designed to exist in tension to ensure both speed and quality:

  • The Product Owner (The Value Navigator): This role is responsible for maximizing the value of the product resulting from the team’s work. They “own” the product vision, prioritize the backlog, and typically communicate requirements through user stories.
  • The Developers (The Builders): Developers in Scrum are meant to be cross-functional and self-organizing. This means they possess all the skills needed—UI, backend, testing—to create a usable increment without depending on outside teams. They are responsible for adhering to a Definition of Done to ensure internal quality.
  • The Scrum Master (The Coach): Misunderstood as a “project manager”, the Scrum Master is actually a servant-leader. Their primary objective is to maximize team effectiveness by removing “impediments” (blockers like legal delays or missing licenses) and coaching the team on Scrum values.

Scrum Artifacts

Scrum manages work through three primary artifacts:

  • Product Backlog: An emergent, ordered list of everything needed to improve the product.
  • Sprint Backlog: A subset of items selected for the current iteration, coupled with an actionable plan for delivery.
  • The Increment: A concrete, verified stepping stone toward the Product Goal. An increment is only “born” once a backlog item meets the team’s Definition of Done—a checklist of quality measures like functional testing, documentation, and performance benchmarks.

Scrum Events

The framework follows a specific rhythm of time-boxed events:

  • The Sprint: A 1–4 week period of uninterrupted development.
  • Sprint Planning: The entire team collaborates to define why the sprint is valuable (the goal), what can be done, and how it will be built.
  • Daily Standup (Daily Scrum): A 15-minute sync where developers discuss what they did yesterday, what they will do today, and any obstacles in their way.
  • Sprint Review: A working session at the end of the sprint where stakeholders provide feedback on the working increment. A good review includes live demos, not just slides.
  • Sprint Retrospective: The team reflects on their process and identifies ways to increase future quality and effectiveness.

Scaling Scrum with SAFe

When a product is too massive for a single team of 7–10 people, organizations often use the Scaled Agile Framework (SAFe). SAFe introduces the Agile Release Train (ART)—a “team of teams” that synchronizes their sprints. It operates on Program Increments (PI), typically lasting 8–12 weeks, which align multiple teams toward quarterly goals. While SAFe provides predictability for Fortune 500 companies, critics sometimes call it “Scrum-but-for-managers” because it can reduce individual team autonomy through heavy planning requirements.

Scrum Quiz

Recalling what you just learned is the best way to form lasting memory. Use this quiz to test your understanding of the Scrum framework, roles, events, and principles.

A software development group realizes their newest feature is confusing users based on early behavioral data. They immediately halt their current plan to redesign the user interface. Which foundational philosophy of their framework does this best illustrate?

Correct Answer:

In an environment that prioritizes agility, the individuals actually building the product must possess a specific dynamic. Which description best captures how this group should operate?

Correct Answer:

The development group is completely blocked because they lack access to a third-party API required for their current iteration. Who is primarily responsible for facilitating the resolution of this organizational bottleneck?

Correct Answer:

To ensure the team is consistently tackling the most crucial problems first, someone must dictate the priority of upcoming work items. Who holds this responsibility?

Correct Answer:

What condition must be strictly satisfied before a newly developed feature is officially considered a completed, verifiable stepping stone toward the ultimate product vision?

Correct Answer:

What is the primary objective of the Daily Scrum?

Correct Answer:

At the conclusion of a work cycle, the team gathers specifically to discuss how they can improve their internal collaboration and technical practices for the next cycle. Which event does this describe?

Correct Answer:

When a massive enterprise needs to coordinate dozens of teams working on the same vast product, they might adopt a ‘team of teams’ approach. According to common critiques, what is a potential drawback of this heavily synchronized model?

Correct Answer:

Extreme Programming (XP)


Overview

Extreme Programming, or XP, emerged as one of the most influential Agile frameworks, originally proposed by software expert Kent Beck. Unlike traditional “Waterfall” models that rely on “Big Upfront Design” and assume stable requirements, XP is built for environments where requirements evolve rapidly as the customer interacts with the product. The core philosophy is to identify software engineering practices that work well and push them to their purest, most “extreme” form.

The primary objectives of XP are to maximize business value, embrace changing requirements even late in development, and minimize the inherent risks of software construction through short, feedback-driven cycles.

Applicability and Limitations

XP is specifically designed for small teams (ideally 4–10 people) located in a single workspace where working software is needed constantly. While it excels at responsiveness, it is often difficult to scale to massive organizations of thousands of people, and it may not be suitable for systems like spacecraft software where the cost of failure is absolute and working software cannot be “continuously” deployed in flight.

XP Practices

The success of XP relies on a set of loosely coupled practices that synergize to improve software quality and team responsiveness.

The Planning Game (and Planning Poker)

The goal of the Planning Game is to align business needs with technical capabilities. It involves two levels of planning:

  • Release Planning: The customer presents user stories, and developers estimate the effort required. This allows the customer to prioritize features based on a balance of business value and technical cost.
  • Iteration Planning: User stories are broken down into technical tasks for a short development cycle (usually 1–4 weeks).

To facilitate estimation, teams often use Planning Poker. Each member holds cards with Fibonacci numbers representing “story points”—imaginary units of effort. If estimates differ wildly, the team discusses the reasoning (e.g., a hidden complexity or a helpful library) until a consensus is reached.

Small Releases

XP teams maximize customer value by releasing working software early, often, and incrementally. This provides rapid feedback and reduces risk by validating real-world assumptions in short cycles rather than waiting years for a final delivery.

Test-Driven Development (TDD)

In XP, testing is not a final phase but a continuous activity. TDD follows a strict “Red-Green-Refactor” rhythm:

  • Red: Write a tiny, failing test for a new requirement.
  • Green: Write the simplest possible code to make that test pass, even taking shortcuts.
  • Refactor: Clean the code and improve the design while ensuring the tests still pass.

TDD ensures high test coverage and results in “living documentation” that describes exactly what the code should do.

Pair Programming

Two developers work together on a single machine. One acts as the Driver (hands on the keyboard, focusing on local implementation), while the other is the Navigator (watching for bugs and thinking about the high-level architecture). Research suggests this improves product quality, reduces risk, and aids in knowledge management.

Continuous Integration (CI)

To avoid the “integration hell” that occurs when developers wait too long to merge their work, XP mandates integrating and testing the entire system multiple times a day. A key benchmark is the 10-minute build: if the build and test process takes longer than 10 minutes, the feedback loop becomes too slow.

Collective Code Ownership

In XP, there are no individual owners of modules; the entire team owns all the code. This increases the bus factor—the number of people who can disappear before the project stalls—and ensures that any team member can fix a bug or improve a module.

Coding Standards

To make collective ownership feasible, the team must adhere to strict coding standards so that the code looks unified, regardless of who wrote it. This reduces the cognitive load during code reviews and maintenance.

Critical Perspectives: Design vs. Agility

A common critique of XP is that focusing solely on implementing features can lead to a violation of the Information Hiding principle. Because TDD focuses on the immediate requirements of a single feature, developers may fail to step back and structure modules around design decisions likely to change.

To mitigate this, XP advocates for “Continuous attention to technical excellence”. While working software is the primary measure of progress, a team that ignores good design will eventually succumb to technical debt—short-term shortcuts that make future changes prohibitively expensive.

Testing


In our quest to construct high-quality software, testing stands as the most popular and essential quality assurance activity. While other techniques like static analysis, model checking, and code reviews are valuable, testing is often the primary pillar of industry-standard quality assurance.

Test Classifications

Regression Testing

As software evolves, we must ensure that new features don’t inadvertently break existing functionality. This is the purpose of regression testing—the repetition of previously executed test cases. In a modern agile environment, these are often automated within a Continuous Integration (CI) pipeline, running every time code is changed

Black-Box and White-Box

When we design tests, we usually adopt one of two mindsets. Black-box testing treats the system as a “black box” where the internal workings are invisible; tests are derived strictly from the requirements or specification to ensure they don’t overfit the implementation. In contrast, white-box testing requires the tester to be aware of the inner workings of the code, deriving tests directly from the implementation to ensure high code coverage.

The Testing Pyramid: Levels of Execution

A robust testing strategy requires a mix of tests at different levels of abstraction.

These levels include:

  • Unit Testing: The execution of a complete class, routine, or small program in isolation.
  • Component Testing: The execution of a class, package, or larger program element, often still in isolation.
  • Integration Testing: The combined execution of multiple classes or packages to ensure they work correctly in collaboration.
  • System Testing: The execution of the software in its final configuration, including all hardware and external software integrations.

Testability

Quality Attributes


While functionality describes exactly what a software system does, quality attributes describe how well the system performs those functions. Quality attributes measure the overarching “goodness” of an architecture along specific dimensions, encompassing critical properties such as extensibility, availability, security, performance, robustness, interoperability, and testability.

Important quality attributes include:

  • Interoperability: the degree to which two or more systems or components can usefully exchange meaningful information via interfaces in a particular context.

  • Testability: degree to which a system or component can be tested via runtime observation, determining how hard it is to write effective tests for a piece of software.

The Architectural Foundation: “Load-Bearing Walls”

Quality attributes are often described as the load-bearing walls of a software system. Just as the structural integrity of a building depends on walls that cannot be easily moved once construction is finished, early architectural decisions strongly impact the possible qualities of a system. Because quality attributes are typically cross-cutting concerns spread throughout the codebase, they are extremely difficult to “add in later” if they were not considered early in the design process.

Categorizing Quality Attributes

Quality attributes can be broadly divided into two categories based on when they manifest and who they impact:

  • Design-Time Attributes: These include qualities like extensibility, changeability, reusability, and testability. These attributes primarily impact developers and designers, and while the end-user may not see them directly, they determine how quickly and safely the system can evolve.
  • Run-Time Attributes: these include qualities like performance, availability, and scalability. These attributes are experienced directly by the user while the program is executing.

Specifying Quality Requirements

To design a system effectively, quality requirements must be measurable and precise rather than broad or abstract. A high-quality specification requires two parts: a scenario and a metric.

  • The Scenario: This describes the specific conditions or environment to which the system must respond, such as the arrival of a certain type of request or a specific environmental deviation.
  • The Metric: This provides a concrete measure of “goodness”. These can be hard thresholds (e.g., “response time < 1s”) or soft goals (e.g., “minimize effort as much as possible”).

For example, a robust specification for a Mars rover would not just say it should be “robust,” but that it must “function normally and send back all information under extreme weather conditions”.

Trade-offs and Synergies

A fundamental reality of software design is that you cannot always maximize all quality attributes simultaneously; they frequently conflict with one another.

  • Common Conflicts: Enhancing security through encryption often decreases performance due to the extra processing required. Similarly, ensuring high reliability (such as through TCP’s message acknowledgments) can reduce performance compared to faster but unreliable protocols like UDP.
  • Synergies: In some cases, attributes support each other. High performance can improve usability by providing faster response times for interactive systems. Furthermore, testability and changeability often synergize, as modular designs that are easy to change also tend to be easier to isolate for testing.

UML


More Notes (WIP):

UML Sequence Diagram

UML State Diagram

UML Class Diagram

1. Classes, Interfaces, and Modifiers

This snippet demonstrates how to define an interface, a class, and use visibility modifiers (+, -, #, ~).

@startuml
interface "Drivable" <<interface>> {
  + startEngine(): void
  + stopEngine(): void
}

class "Car" {
  - make: String
  - model: String
  # year: int
  ~ packageLevelAttribute: String
  
  + startEngine(): void
  + getMake(): String
}
@enduml

2. Relationships

PlantUML uses different arrow styles to represent the various relationships. The direction of the arrow generally goes from the “child” or “part” to the “parent” or “whole.”

Generalization (Inheritance)

Use <|-- to draw a solid line with an empty, closed arrowhead.

@startuml
class "Vehicle" {
  + move(): void
}
class "Car"
class "Motorcycle"

Vehicle <|-- Car
Vehicle <|-- Motorcycle
@enduml

Interface Realization (Implementation)

Use <|.. to draw a dashed line with an empty, closed arrowhead.

@startuml
interface "Drivable"
class "Car"

Drivable <|.. Car
@enduml

Association and Multiplicities

Use -- for a standard solid line. You can add quotes around numbers at either end to define the multiplicities, and a colon followed by text to label the association.

@startuml
class "Teacher"
class "Course"
class "Student"

Teacher "1" -- "0..*" Course : teaches >
Course "1..*" -- "0..*" Student : enrolled in >
@enduml

Aggregation

Use o-- to draw a solid line with an empty diamond pointing to the “whole” class.

@startuml
class "Department"
class "Professor"

Department o-- Professor
@enduml

Composition

Use *-- to draw a solid line with a filled (black) diamond pointing to the “whole” class.

@startuml
class "House"
class "Room"

House *-- "1..*" Room : contains
@enduml

3. Putting It All Together: A Mini E-commerce Example

Here is a consolidated PlantUML diagram showing how these concepts interact in a simple system design.

@startuml
interface "PaymentMethod" <<interface>> {
  + pay(amount: double): boolean
}

class "CreditCard" {
  - cardNumber: String
  - expirationDate: String
  + pay(amount: double): boolean
}

class "Customer" {
  - name: String
  - email: String
  + placeOrder(): void
}

class "Order" {
  - orderId: int
  - totalAmount: double
}

class "OrderItem" {
  - productId: int
  - quantity: int
}

' Relationships
PaymentMethod <|.. CreditCard : realizes
Customer "1" -- "0..*" Order : places >
Order *-- "1..*" OrderItem : is composed of >
Customer "1" -- "0..*" PaymentMethod : uses >

@enduml

Would you like me to show you how to add more advanced PlantUML features, like notes, coloring, or packages to organize your classes?

Class Diagrams 

Class diagrams represent classes and their interactions.

Classes

Classes are displayed as rectangles with one to three different sections that are each separated by a horizontal line.

The top section is always the name of the class. If the class is abstract, the name is in italics. 

The middle section indicates attributes of the class (i.e., member variables). 

The bottom section should include all methods that are implemented in this class (i.e., for which the implementation of the class contains a method definition). 

Inheritance is visualized using an arrow with an empty triage pointing to the super class. 

Attributes and methods can be marked as public (+), private (-), or protected (#), to indicate the visibility. Hint: Avoid public attributes, as this leads to bad design. (Public means every class has access, private means only this class has access, protected means this class and its sub classes have access) 

When a class uses an association, the name and visibility of the attribute can be written either next to the association or in the attribute section, or both (but only if it is done consistently). Writing it on the Association is more common since it increases the readability of the diagram.

Please include types for arguments and a meaningful parameter name. Include return types in case the method returns something (e.g., + calculateTax(income: int): int

Interfaces

Interfaces are classes that do not have any method definitions and no attributes. Interfaces only contain method declarations. Interfaces are visualized using the <<interface>> stereotype

To realize an interface, use then arrow with an empty triage pointing to the interface and a dashed line.

Sequence Diagrams 

Sequence diagrams display the interaction between concrete objects (or component instances). 

They show one particular example of interactions (potentially with optional, alternative, or looped behavior when necessary). Sequence diagrams are not intended to show ALL possible behaviors since this would become very complex and then hard to understand.

Objects / component instances are displayed in rectangles with the label following this pattern: objectName: ClassName. If the name of the object is irrelevant, then you can just write : Classname

When showing interactions between objects then all arrows in the sequence diagram represent method calls being made between the two objects. So an arrow from the client object with the name handleInput to the state objects means that somewhere in the code of the class of which client is an instance of, there is a method call to the handleInput method on the object state. Important: These are interactions between particular objects, not just generally between classes. It’s always on concrete instance of this class. 

The names shown on the arrows have to be consistent with the method names shown in the class diagram, including the number or arguments, order of arguments, and types of arguments. Whenever an arrow with method x and arguments of type Y and Z are received by an object o, then either the class of which o is an instance of or one of its super classes needs to have an implementation of x(Y,Z).     

It is a modeling choice to decide whether you want to include concrete values (e.g., caclulateTax(1400)) or meaningful variable names (e.g., calculateTax(income)). If you reference a real variable that has been used before, please make sure to ensure it is the same one and it has the right type. 

State Machine Diagrams 

State machines model the transitions between different states. States are modeled either as oval, rectangles with rounded corners, or circles. 

Transitions follow the patter [condition] trigger / action

State machines always need an initial state but don’t always need a final state. 

User Stories


User stories are the most commonly used format to specify requirements in a light-weight, informal way (particulalry in Agile projects). Each user story is a high-level description of a software feature written from the perspective of the end-user.

User stories act as placeholders for a conversation between the technical team and the “business” side to ensure both parties understand the why and what of a feature.

Format

User stories follow this format:


As a [user role],

I want [to perform an action]

so that [I can achieve a goal]


For example:

(Smart Grocery Application): As a home cook, I want to swap out ingredients in a recipe so that I can accommodate my dietary restrictions and utilize what I already have in my kitchen.

(Travel Itinerary Planner): As a frequent traveler, I want to discover unique, locally hosted activities so that I can experience the authentic culture of my destination rather than just the standard tourist traps.

This structure makes the team to identify not just the “what”, but also the “who” and — most importantly — the “why”.

The main requirement of the user story is captured in the I want part. The so that part clarifies the goal the user wants to achieve. It does not add additional requirements or constraints to the described requirements.

Be specific about the actor. Avoid generic labels like “user” in the As a clause. Instead, name the specific role that benefits from the feature (e.g., “job seeker”, “hiring manager”, “store owner”). A precise actor clarifies who needs the feature and why, helps the team understand the context, and prevents stories from becoming vague catch-alls. If you find yourself writing “As a user,” ask: which user?

Acceptance Criteria

While the story itself is informal, we make it actionable using Acceptance Criteria. They define the boundaries of the feature and act as a checklist to determine if a story is “done”. Acceptance criteria define the scope of a user story.

They follow this format:


Given [pre-condition / initial state]

When [action]

Then [post-condition / outcome]


For example:

(Smart Grocery Application): As a home cook, I want to swap out ingredients in a recipe so that I can accommodate my dietary restrictions and utilize what I already have in my kitchen.

  • Given the user is viewing a recipe’s ingredient list, when they tap on a specific ingredient, then a modal should appear suggesting a list of viable alternatives.
  • Given the user selects a substitute from the alternatives list, when they confirm the swap, then the recipe’s required quantities and nutritional estimates should recalculate and update on the screen.
  • Given the user has modified a recipe with substitutions, when they tap the “Save to My Cookbook” button, then the customized version of the recipe should be stored in their personal profile without altering the original public recipe.

These acceptance criteria add clarity to the user story by defining the specific conditions under which the feature should work as expected. They also help to identify potential edge cases and constraints that need to be considered during development. The acceptance criteria define the scope of conditions that check whether an implementation is “correct” and meets the user’s needs. So naturally, acceptance criteria must be specific enough to be testable but should not be overly prescriptive about the implementation details, not to constraint the developers more than really needed to describe the true user need.

Here is another example:

(Travel Itinerary Planner): As a frequent traveler, I want to discover unique, locally hosted activities so that I can experience the authentic culture of my destination rather than just the standard tourist traps.

  • Given the user has set their upcoming trip destination to a city, when they navigate to the “Local Experiences” tab, then they should see a dynamically populated list of activities hosted by verified local residents.
  • Given the user is browsing the experiences list, when they apply the “Under $50” budget filter, then the list should refresh to display only the activities that fall within that price range.
  • Given the user selects a specific local experience, when they tap “Check Availability”, then a calendar widget should expand displaying open booking slots for their specific travel dates.

INVEST

To evaluate if a user story is well-written, we apply the INVEST criteria:

  • Independent: Stories should not depend on each other so they can be implemented and released in any order.
  • Negotiable: They capture the essence of a need without dictating specific design decisions (like which database to use).
  • Valuable: The feature must deliver actual benefit to the user, not just the developer.
  • Estimable: The scope must be clear enough for developers to predict the effort required.
  • Small: A story should be a manageable chunk of work that isn’t easily split into smaller, still-valuable pieces.
  • Testable: It must be verifiable through its acceptance criteria.

We will now look at these criteria in more detail below.

Independent

An independent story does not overlap with or depend on other stories—it can be scheduled and implemented in any order.

What it is and Why it Matters The “Independent” criterion states that user stories should not overlap in concept and should be schedulable and implementable in any order (Wake 2003). An independent story can be understood, tracked, implemented, and tested on its own, without requiring other stories to be completed first.

This criterion matters for several fundamental reasons:

  • Flexible Prioritization: Independent stories allow the business to prioritize the backlog based strictly on value, rather than being constrained by technical dependencies (Wake 2003). Without independence, a high-priority story might be blocked by a low-priority one.
  • Accurate Estimation: When stories overlap or depend on each other, their estimates become entangled. For example, if paying by Visa and paying by MasterCard are separate stories, the first one implemented bears the infrastructure cost, making the second one much cheaper (Cohn 2004). This skews estimates.
  • Reduced Confusion: By avoiding overlap, independent stories reduce places where descriptions contradict each other and make it easier to verify that all needed functionality has been described (Wake 2003).

How to Evaluate It To determine if a user story is independent, ask:

  1. Does this story overlap with another story? If two stories share underlying capabilities (e.g., both involve “sending a message”), they have overlap dependency—the most painful form (Wake 2003).
  2. Must this story be implemented before or after another? If so, there is an order dependency. While less harmful than overlap (the business often naturally schedules these correctly), it still constrains planning (Wake 2003).
  3. Was this story split along technical boundaries? If one story covers the UI layer and another covers the database layer for the same feature, they are interdependent and neither delivers value alone (Cohn 2004).

How to Improve It If stories violate the Independent criterion, you can improve them using these techniques:

  • Combine Interdependent Stories: If two stories are too entangled to estimate separately, merge them into a single story. For example, instead of separate stories for Visa, MasterCard, and American Express payments, combine them: “A company can pay for a job posting with a credit card” (Cohn 2004).
  • Partition Along Different Dimensions: If combining makes the story too large, re-split along a different dimension. For overlapping email stories like “Team member sends and receives messages” and “Team member sends and replies to messages”, repartition by action: “Team member sends message”, “Team member receives message”, “Team member replies to message” (Wake 2003).
  • Slice Vertically: When stories have been split along technical layers (UI vs. database), re-slice them as vertical “slices of cake” that cut through all layers. Instead of “Job Seeker fills out a resume form” and “Resume data is written to the database”, write “Job Seeker can submit a resume with basic information” (Cohn 2004).

Examples of Stories Violating ONLY the Independent Criterion

Example 1: Overlap Dependency

Story A: As a team member, I want to send and receive messages so that I can communicate with my colleagues.”

  • Given I am on the messaging page, When I compose a message and click “Send”, Then the message appears in the recipient’s inbox.
  • Given a colleague has sent me a message, When I open my inbox, Then I can read the message.

Story B: As a team member, I want to reply to messages so that I can indicate which message I am responding to.”

  • Given I have received a message, When I click the “Reply” button and submit my response, Then the reply is sent to the original sender.
  • Given the reply has been received, When the original sender views the message, Then it is displayed has a reply to the original message.
  • Negotiable: Yes. Neither story dictates a specific UI or technology.
  • Valuable: Yes. Communication features are clearly valuable to users.
  • Estimable: Difficult. The overlapping “send” capability makes it unclear how to estimate each story independently.
  • Small: Yes. Each story is as small as it can be without losing value. Sending without receiving would be incomplete and thus not valuable, so we cannot split story A into seperate stories.
  • Testable: Yes. Clear acceptance criteria can be written for sending, receiving, and replying.
  • Why it violates Independent: Both stories include “sending a message.” If Story A is implemented first, parts of Story B are already done. If Story B is implemented first, parts of Story A are already done. This creates confusion about what is covered and makes estimation unreliable.
  • How to fix it: Repartition into three non-overlapping stories: “As a team member, I want to send a message”, “As a team member, I want to receive messages”, and “As a team member, I want to reply to a message.”

Example 2: Technical (Horizontal) Splitting

Story A: As a job seeker, I want to fill out a resume form so that I can enter my information.”

  • Given I am on the resume page, When I fill in my name, address, and education, Then the form displays my entered information.

Story B: As a job seeker, I want my resume data to be saved so that it is available when I return.”

  • Given I have filled out the resume form, When I click “Save”, Then my resume data is available when I log back in.
  • Negotiable: No. Both stories dictate internal technical steps rather than user-facing capabilities.
  • Valuable: No. Neither story delivers value on its own—a form that does not save is useless, and saving data without a form to collect it is equally useless.
  • Estimable: Yes. Developers can estimate each technical task.
  • Small: Yes. Each is a small piece of work.
  • Testable: Yes. Each can be verified in isolation.
  • Why it violates Independent: Story B is meaningless without Story A, and Story A is useless without Story B. They are completely interdependent because the feature was split along technical boundaries (UI layer vs. persistence layer) instead of user-facing functionality (Cohn 2004).
  • How to fix it: Combine into a single vertical slice: “As a job seeker, I want to submit a resume with basic information (name, address, education) so that employers can find me.” This cuts through all layers and delivers value independently (Cohn 2004).

Negotiable

A negotiable story captures the essence of a user’s need without locking in specific design or technology decisions—the details are worked out collaboratively.

What it is and Why it Matters The “Negotiable” criterion states that a user story is not an explicit contract for features; rather, it captures the essence of a user’s need, leaving the details to be co-created by the customer and the development team during development (Wake 2003). A good story captures the essence, not the details (see also “Requirements Vs. Design”).

This criterion matters for several fundamental reasons:

  • Enabling Collaboration: Because stories are intentionally incomplete, the team is forced to have conversations to fill in the details. Ron Jeffries describes this through the three C’s: Card (the story text), Conversation (the discussion), and Confirmation (the acceptance tests) (Cohn 2004). The card is merely a token promising a future conversation (Wake 2003).
  • Evolutionary Design: High-level stories define capabilities without over-constraining the implementation approach (Wake 2003). This leaves room to evolve the solution from a basic form to an advanced form as the team learns more about the system’s needs.
  • Avoiding False Precision: Including too many details early creates a dangerous illusion of precision (Cohn 2004). It misleads readers into believing the requirement is finalized, which discourages necessary conversations and adaptation.

How to Evaluate It To determine if a user story is negotiable, ask:

  1. Does this story dictate a specific technology or design decision? Words like “MongoDB”, “HTTPS”, “REST API”, or “dropdown menu” in a story are red flags that it has left the space of requirements and entered the space of design.
  2. Could the development team solve this problem using a completely different technology or layout, and would the user still be happy? If the answer is yes, the story is negotiable. If the answer is no, the story is over-constrained.
  3. Does the story include UI details? Embedding user interface specifics (e.g., “a print dialog with a printer list”) introduces premature assumptions before the team fully understands the business goals (Cohn 2004).

How to Improve It If a story violates the Negotiable criterion, you can improve it using these techniques:

  • Focus on the “Why”: Use “So that” clauses to clarify the underlying goal, which allows the team to negotiate the “How”.
  • Specify What, Not How: Replace technology-specific language with the user need it serves. Instead of “use HTTPS”, write “keep data I send and receive confidential.”
  • Define Acceptance Criteria, Not Steps: Define the outcomes that must be true, rather than the specific UI clicks or database queries required.
  • Keep the UI Out as Long as Possible: Avoid embedding interface details into stories early in the project (Cohn 2004). Focus on what the user needs to accomplish, not the specific controls they will use.

Examples of Stories Violating ONLY the Negotiable Criterion

Example 1: The Technology-Specific Story

As a subscriber, I want my profile settings saved in a MongoDB database so that they load quickly the next time I log in.”

  • Given I am logged in and I change my profile settings, When I log out and log back in, Then my profile settings are still applied.
  • Independent: Yes. Saving profile settings does not depend on other stories.
  • Valuable: Yes. Remembering user settings is clearly valuable.
  • Estimable: Yes. A developer can estimate the effort to implement settings persistence.
  • Small: Yes. This is a focused piece of work.
  • Testable: Yes. You can verify that settings persist across sessions.
  • Why it violates Negotiable: Specifying “MongoDB” is a design decision. The user does not care where the data lives. The engineering team might realize that a relational SQL database or local browser caching is a much better fit for the application’s architecture.
  • How to fix it: As a subscriber, I want the system to remember my profile settings so that I don’t have to re-enter them every time I log in.”

Example 2: The UI-Specific Story

As a student, I want the website to use HTTPS so that my data is safe.”

  • Given I am submitting personal data on the website, When the data is transmitted to the server, Then the connection uses HTTPS encryption.
  • Independent: Yes. Security does not depend on other stories.
  • Valuable: Yes. Data safety is clearly valuable to the user.
  • Estimable: Yes. Enabling HTTPS is a well-understood task.
  • Small: Yes. This is a single, focused change.
  • Testable: Yes. You can verify that traffic is encrypted.
  • Why it violates Negotiable: “HTTPS” is a specific design decision. The user’s actual need is data confidentiality, which could be achieved in multiple ways depending on the system’s architecture.
  • How to fix it: As a student, I want the website to keep data I send and receive confidential so that my privacy is ensured.”

Valuable

A valuable story delivers tangible benefit to the customer, purchaser, or user—not just to the development team.

What it is and Why it Matters The “Valuable” criterion states that every user story must deliver tangible value to the customer, purchaser, or user—not just to the development team (Wake 2003). A good story focuses on the external impact of the software in the real world: if we frame stories so their impact is clear, product owners and users can understand what the stories bring and make good prioritization choices (Wake 2003).

This criterion matters for several fundamental reasons:

  • Informed Prioritization: The product owner prioritizes the backlog by weighing each story’s value against its cost. If a story’s business value is opaque—because it is written in technical jargon—the customer cannot make intelligent scheduling decisions (Cohn 2004).
  • Avoiding Waste: Stories that serve only the development team (e.g., refactoring for its own sake, adopting a trendy technology) consume iteration capacity without moving the product closer to its users’ goals. The IRACIS framework provides a useful lens for value: does the story Increase Revenue, Avoid Costs, or Improve Service? (Wake 2003)
  • User vs. Purchaser Value: It is tempting to say every story must be valued by end-users, but that is not always correct. In enterprise environments, the purchaser may value stories that end-users do not care about (e.g., “All configuration is read from a central location” matters to the IT department managing 5,000 machines, not to daily users) (Cohn 2004).

How to Evaluate It To determine if a user story is valuable, ask:

  1. Would the customer or user care if this story were dropped? If only developers would notice, the story likely lacks user-facing value.
  2. Can the customer prioritize this story against others? If the story is written in “techno-speak” (e.g., “All connections go through a connection pool”), the customer cannot weigh its importance (Cohn 2004).
  3. Does this story describe an external effect or an internal implementation detail? Valuable stories describe what happens on the edge of the system—the effects of the software in the world—not how the system is built internally (Wake 2003).

How to Improve It If stories violate the Valuable criterion, you can improve them using these techniques:

  • Rewrite for External Impact: Translate the technical requirement into a statement of benefit for the user. Instead of “All connections to the database are through a connection pool”, write “Up to fifty users should be able to use the application with a five-user database license” (Cohn 2004).
  • Let the Customer Write: The most effective way to ensure a story is valuable is to have the customer write it in the language of the business, rather than in technical jargon (Cohn 2004).
  • Focus on the “So That”: A well-written “so that” clause forces the author to articulate the real-world benefit. If you cannot complete “so that [some user benefit]” without referencing technology, the story is likely not valuable.
  • Complete the Acceptance Criteria: A story may appear valuable but have incomplete acceptance criteria that leave out essential functionality, effectively making the delivered feature useless.

Examples of Stories Violating ONLY the Valuable Criterion

Example 1: The Developer-Centric Story

As a developer, I want to rewrite the core authentication API in Rust so that I can use a more modern programming language.”

  • Given the authentication API currently runs on Node.js, When a developer deploys the new Rust-based API, Then all existing authentication endpoints return identical responses.
  • Independent: Yes. Rewriting the auth API does not depend on other stories.
  • Negotiable: Yes. The story is phrased as a goal (rewrite auth), leaving room to discuss scope and approach.
  • Estimable: Yes. A developer experienced with Rust can estimate the effort of a rewrite.
  • Small: Yes. Rewriting a single API component can fit within a sprint.
  • Testable: Yes. You can verify the new API passes all existing authentication tests.
  • Why it violates Valuable: The story is written entirely from the developer’s perspective. The user does not care which programming language the API uses. The “so that” clause (“use a more modern programming language”) describes a developer preference, not a user benefit (Cohn 2004).
  • How to fix it: If there is a legitimate user-facing reason (e.g., performance), rewrite the story around that benefit: As a registered member, I want to log in without noticeable delay so that I can start using the application immediately.”

Example 2: The Incomplete Story

As a smart home owner, I want to schedule my porch lights to turn on automatically at a specific time so that I don’t have to walk up to a dark house in the evening.” Given I am logged into the smart home mobile app, When I set the porch light schedule to turn on at 6:00 PM, Then the porch lights will illuminate at exactly 6:00 PM every day.

  • Independent: Yes. Scheduling lights does not depend on other stories.
  • Negotiable: Yes. The specific UI and scheduling mechanism are open to discussion.
  • Estimable: Yes. Implementing a time-based trigger is well-understood work.
  • Small: Yes. A single scheduling feature fits within a sprint.
  • Testable: Yes. The acceptance criteria define a clear pass/fail condition.
  • Why it violates Valuable: On first glance, this story looks valuable. But the acceptance criteria are missing the ability to turn off the lights. If lights stay on forever, they waste energy and the feature becomes a nuisance rather than a benefit. The story as written delivers incomplete value because its acceptance criteria do not capture the full scope needed to make the feature genuinely useful.
  • How to fix it: Add the missing acceptance criterion: “Given I am logged into the smart home mobile app, When I set the porch light schedule to turn off at 6:00 AM and the lights are illuminated, Then the porch lights will turn off at 6:00 AM.” Now the story delivers complete value.

Estimable

An estimable story has a scope clear enough for the development team to make a reasonable judgment about the effort required.

What it is and Why it Matters The “Estimable” criterion states that the development team must be able to make a reasonable judgment about a story’s size, cost, or time to deliver (Wake 2003). While precision is not the goal, the estimate must be useful enough for the product owner to prioritize the story against other work (Cohn 2004).

This criterion matters for several fundamental reasons:

  • Enabling Prioritization: The product owner ranks stories by comparing value to cost. If a story cannot be estimated, the cost side of this equation is unknown, making informed prioritization impossible (Cohn 2004).
  • Supporting Planning: Stories that cannot be estimated cannot be reliably scheduled into an iteration. Without sizing information, the team risks committing to more (or less) work than they can deliver.
  • Surfacing Unknowns Early: An unestimable story is a signal that something important is not understood—either the domain, the technology, or the scope. Recognizing this early prevents costly surprises later.

How to Evaluate It Developers generally cannot estimate a story for one of three reasons (Cohn 2004):

  1. Lack of Domain Knowledge: The developers do not understand the business context. For example, a story saying “New users are given a diabetic screening” could mean a simple web questionnaire or an at-home physical testing kit—without clarification, no estimate is possible (Cohn 2004).
  2. Lack of Technical Knowledge: The team understands the requirement but has never worked with the required technology. For example, a team asked to expose a gRPC API when no one has experience with Protocol Buffers or gRPC cannot estimate the work (Cohn 2004).
  3. The Story is Too Big: An epic like “A job seeker can find a job” encompasses so many sub-tasks and unknowns that it cannot be meaningfully sized as a single unit (Cohn 2004).

How to Improve It The approach to fixing an unestimable story depends on which barrier is blocking estimation:

  • Conversation (for Domain Knowledge Gaps): Have the developers discuss the story directly with the customer. A brief conversation often reveals that the requirement is simpler (or more complex) than assumed, making estimation possible (Cohn 2004).
  • Spike (for Technical Knowledge Gaps): Split the story into two: an investigative spike—a brief, time-boxed experiment to learn about the unknown technology—and the actual implementation story. The spike itself is always given a defined maximum time (e.g., “Spend exactly two days investigating credit card processing”), which makes it estimable. Once the spike is complete, the team has enough knowledge to estimate the real story (Cohn 2004).
  • Disaggregate (for Stories That Are Too Big): Break the epic into smaller, constituent stories. Each smaller piece isolates a specific slice of functionality, reducing the cognitive load and making estimation tractable (Cohn 2004).

Examples of Stories Violating ONLY the Estimable Criterion

Example 1: The Unknown Domain

As a patient, I want to receive a personalized wellness screening so that I can understand my health risks.”

  • Given I am a new patient registering on the platform, When I complete the wellness screening, Then I receive a personalized health risk summary based on my answers.
  • Independent: Yes. The screening feature does not depend on other stories.
  • Negotiable: Yes. The specific questions and screening logic are open to discussion.
  • Valuable: Yes. Personalized health screening is clearly valuable to patients.
  • Small: Yes. A single screening workflow can fit within a sprint—once the scope is clarified.
  • Testable: Yes. Acceptance criteria can define specific screening outcomes for specific patient profiles.
  • Why it violates Estimable: The developers do not know what “personalized wellness screening” means in this context. It could be a simple 5-question web form or a complex algorithm that integrates with lab data. Without domain knowledge, the team cannot estimate the effort (Cohn 2004).
  • How to fix it: Have the developers sit down with the customer (e.g., a qualified nurse or medical expert) to clarify the scope. Once the team learns it is a simple web questionnaire, they can estimate it confidently.

Example 2: The Unknown Technology

As an enterprise customer, I want to access the system’s data through a gRPC API so that I can integrate it with my existing microservices infrastructure.”

  • Given an enterprise client sends a gRPC request for user data, When the system processes the request, Then the system returns the requested data in the correct Protobuf-defined format.
  • Independent: Yes. Adding an integration interface does not depend on other stories.
  • Negotiable: Partially. The customer has specified gRPC, but the service contract and data schema are open to discussion.
  • Valuable: Yes. Enterprise integration is clearly valuable to the purchasing organization.
  • Small: Yes. A single service endpoint can fit within a sprint—once the team understands the technology.
  • Testable: Yes. You can verify the interface returns the correct data in the correct format.
  • Why it violates Estimable: No one on the development team has ever built a gRPC service or worked with Protocol Buffers. They understand what the customer wants but have no experience with the technology required to deliver it, making any estimate unreliable (Cohn 2004).
  • How to fix it: Split into two stories: (1) a time-boxed spike—”Investigate gRPC integration: spend at most two days building a proof-of-concept service”—and (2) the actual implementation story. After the spike, the team has enough knowledge to estimate the real work (Cohn 2004).

Small

A small story is a manageable chunk of work that can be completed within a single iteration—not so large it becomes an epic, not so small it loses meaningful context. A user story should be as small as it can be while still delivering value.

What it is and Why it Matters The “Small” criterion states that a user story should be appropriately sized so that it can be comfortably completed by the development team within a single iteration (Cohn 2004). Stories typically represent at most a few person-weeks of work; some teams restrict them to a few person-days (Wake 2003). If a story is too large, it is called an epic and must be broken down. If a story is too small, it should be combined with related stories.

This criterion matters for several fundamental reasons:

  • Predictability: Large stories are notoriously difficult to estimate accurately. The smaller the story, the higher the confidence the team has in their estimate of the effort required (Cohn 2004).
  • Risk Reduction: If a massive story spans an entire sprint (or spills over into multiple sprints), the team risks delivering zero value if they hit a roadblock. Smaller stories ensure a steady, continuous flow of delivered value.
  • Faster Feedback: Smaller stories reach a “Done” state faster, meaning they can be tested, reviewed by the product owner, and put in front of users much sooner to gather valuable feedback.

How to Evaluate It To determine if a user story is appropriately sized, ask:

  1. Can it be completed in one sprint? If the answer is no, or “maybe, if everything goes perfectly,” the story is too big. It is an epic and must be split (Cohn 2004).
  2. Is it a compound story? Words like and, or, and but in the story description (e.g., “I want to register and manage my profile and upload photos”) often indicate that multiple stories are hiding inside one. A compound story is an epic that aggregates multiple easily identifiable shorter stories (Cohn 2004).
  3. Is it a complex story? If the story is large because of inherent uncertainty (new technology, novel algorithm), it is a complex story and should be split into a spike and an implementation story (Cohn 2004).
  4. Is it too small? If the administrative overhead of writing and estimating the story takes longer than implementing it, the story is too small and should be combined with related stories (Cohn 2004).

How to Improve It The approach to fixing a story that violates the Small criterion depends on whether it is too big or too small:

Stories that are too big:

  • Split by Workflow Steps (CRUD): Instead of “As a job seeker, I want to manage my resume,” split along operations: create, edit, delete, and manage multiple resumes (Cohn 2004).
  • Split by Data Boundaries: Instead of splitting by operation, split by the data involved: “add/edit education”, “add/edit job history”, “add/edit salary” (Cohn 2004).
  • Slice the Cake (Vertical Slicing): Never split along technical boundaries (one story for UI, one for database). Instead, split into thin end-to-end “vertical slices” where each story touches every architectural layer and delivers complete, albeit narrow, functionality (Cohn 2004).
  • Split by Happy/Sad Paths: Build the “happy path” (successful transaction) as one story, and handle the error states (declined cards, expired sessions) in subsequent stories.

Stories that are too small:

  • Combine Related Stories: Merge tiny, related items (e.g., a batch of small UI tweaks or minor bug fixes) into a single story representing a half-day to several days of work (Cohn 2004).

Examples of Stories Violating ONLY the Small Criterion

Example 1: The Epic (Too Big)

As a traveler, I want to plan a vacation so that I can book all the arrangements I need in one place.”

  • Given I have selected travel dates and a destination, When I search for vacation packages, Then I see available flights, hotels, and rental cars with pricing.
  • Given I have selected a flight, hotel, and rental car, When I click “Book”, Then all reservations are confirmed and I receive a booking confirmation email.
  • Independent: Yes. Planning a vacation does not overlap with other stories.
  • Negotiable: Yes. The specific features and UI are open to discussion.
  • Valuable: Yes. End-to-end vacation planning is clearly valuable to travelers.
  • Estimable: No. The scope is so vast that developers cannot reliably predict the effort. (Violations of Small often cause violations of Estimable, since epics contain hidden complexity.)
  • Testable: Yes. Acceptance criteria can be written for individual planning features.
  • Why it violates Small: “Planning a vacation” involves searching for flights, comparing hotels, booking rental cars, managing an itinerary, handling payments, and much more. This is an epic containing many stories. It cannot be completed in a single sprint (Cohn 2004).
  • How to fix it: Disaggregate into smaller vertical slices: “As a traveler, I want to search for flights by date and destination so that I can find available options”, “As a traveler, I want to compare hotel prices for my destination so that I can choose one within my budget”, etc.

Example 2: The Micro-Story (Too Small)

As a job seeker, I want to edit the date for each community service entry on my resume so that I can correct mistakes.”

  • Given I am viewing a community service entry on my resume, When I change the date field and click “Save”, Then the updated date is displayed on my resume.
  • Independent: Yes. Editing a single date field does not depend on other stories.
  • Negotiable: Yes. The exact editing interaction is open to discussion.
  • Valuable: Yes. Correcting resume data is valuable to the user.
  • Estimable: Yes. Editing a single field is trivially estimable.
  • Testable: Yes. Clear pass/fail criteria can be written.
  • Why it violates Small: This story is too small. The administrative overhead of writing, estimating, and tracking this story card takes longer than actually implementing the change. Having dozens of stories at this granularity buries the team in disconnected details—what Wake calls a “bag of leaves” (Wake 2003).
  • How to fix it: Combine with related micro-stories into a single meaningful story: “As a job seeker, I want to edit all fields of my community service entries so that I can keep my resume accurate.” (Cohn 2004)

Testable

A testable story has clear, objective, and measurable acceptance criteria that allow the team to verify definitively when the work is done.

What it is and Why it Matters The “Testable” criterion dictates that a user story must have clear, objective, and measurable conditions that allow the team to verify when the work is officially complete. If a story is not testable, it can never truly be considered “Done.”

This criterion matters for several crucial reasons:

  • Shared Understanding: It forces the product owner and the development team to align on the exact expectations. It removes ambiguity and prevents the dreaded “that’s not what I meant” conversation at the end of a sprint.
  • Proving Value: A user story represents a slice of business value. If you cannot test the story, you cannot prove that it successfully delivers that value to the user.
  • Enabling Quality Assurance: Testable stories allow QA engineers (and developers practicing Test-Driven Development) to write their test cases—whether manual or automated—before a single line of production code is written.

How to Evaluate It To determine if a user story is testable, ask yourself the following questions:

  1. Can I write a definitive pass/fail test for this? If the answer relies on someone’s opinion or mood, it is not testable.
  2. Does the story contain “weasel words”? Look out for subjective adjectives and adverbs like fast, easy, intuitive, beautiful, modern, user-friendly, robust, or seamless. These words are red flags that the story lacks objective boundaries.
  3. Are the Acceptance Criteria clear? Does the story have defined boundaries that outline specific scenarios and edge cases?

How to Improve It If you find a story that violates the Testable criterion, you can improve it by replacing subjective language with quantifiable metrics and concrete scenarios:

  • Quantify Adjectives: Replace subjective terms with hard numbers. Change “loads fast” to “loads in under 2 seconds.” Change “supports a lot of users” to “supports 10,000 concurrent users.”
  • Use the Given/When/Then Format: Borrow from Behavior-Driven Development (BDD) to write clear acceptance criteria. Establish the starting state (Given), the action taken (When), and the expected, observable outcome (Then).
  • Define “Intuitive” or “Easy”: If the goal is a “user-friendly” interface, make it testable by tying it to a metric, such as: “A new user can complete the checkout process in fewer than 3 clicks without relying on a help menu.”

Examples of Stories Violating ONLY the Testable Criterion

Below are two user stories that are not testable but still satisfy (most) other INVEST criteria.

Example 1: The Subjective UI Requirement

As a marketing manager, I want the new campaign landing page to feature a gorgeous and modern design, so that it appeals to our younger demographic.”

  • Given the landing page is deployed, When a visitor from the 18-24 demographic views it, Then the design looks gorgeous and modern.
  • Independent: Yes. It doesn’t inherently rely on other features being built first.
  • Negotiable: Yes. The exact layout and tech used to build it are open to discussion.
  • Valuable: Yes. A landing page to attract a younger demographic provides clear business value.
  • Estimable: Yes. Generally, a frontend developer can estimate the effort to build a standard landing page.
  • Small: Yes. Building a single landing page easily fits within a single sprint.
  • Why it violates Testable: “Gorgeous,” “modern,” and “appeals to” are completely subjective. What one developer thinks is modern, the marketing manager might think is ugly.
  • How to fix it: Tie it to a specific, measurable design system or user-testing metric. (e.g., “Acceptance Criteria: The design strictly adheres to the new V2 Brand Guidelines and passes a 5-second usability test with a 4/5 rating from a focus group of 18-24 year olds.”)

Example 2: The Vague Performance Requirement

As a data analyst, I want the monthly sales report to generate instantly, so that my workflow isn’t interrupted by loading screens.”

  • Given the database contains 5 years of sales data, When the analyst requests the monthly sales report, Then the report generates instantly.
  • Independent: Yes. Optimizing or building this report can be done independently.
  • Negotiable: Yes. The team can negotiate how to achieve the speed (e.g., caching, database indexing, background processing).
  • Valuable: Yes. Saving the analyst’s time is a clear operational benefit.
  • Small: Yes. It is a focused optimization on a single report.
  • Why it violates Testable: “Instantly” is physically impossible in computing, and it is a highly subjective standard. Does instantly mean 0.1 seconds, or 1.5 seconds? Without a benchmark, a test script cannot verify if the feature passes or fails.
  • Estimable: No. Without a clear definition of “instantly”, the team cannot estimate the effort required to build the feature. Violations of testable are often also not estimable. In the example above, the Subjective UI was still estiable, because independent of the specific definition of “modern”, the implementation effort would not change signifiantly, just the specific UI that would be chosen would change.
  • How to fix it: Replace the subjective word with a quantifiable service level indicator. (e.g., “Acceptance Criteria: Given the database contains 5 years of sales data, when the analyst requests the monthly sales report, then the data renders on screen in under 2.5 seconds at the 95th percentile.”)

Example 3: The Subjective Audio Requirement

As a podcast listener, I want the app’s default intro chime to play at a pleasant volume, so that it doesn’t startle me when I open the app.”

  • Given I open the app for the first time, When the intro chime plays, Then the volume is at a pleasant level.
  • Independent: Yes. Adjusting the audio volume doesn’t rely on other features.
  • Negotiable: Yes. The exact decibel level or method of adjustment is open to discussion.
  • Valuable: Yes. Improving user comfort directly enhances the user experience.
  • Estimable: Yes. Changing a default audio volume variable or asset is a trivial, highly predictable task (e.g., a 1-point story). The developers know exactly how much effort is involved.
  • Small: Yes. It will take a few minutes to implement.
  • Why it violates Testable: “Pleasant volume” is entirely subjective. A volume that is pleasant in a quiet library will be inaudible on a noisy subway. Because there is no objective baseline, QA cannot definitively pass or fail the test.
  • How to fix it: “Acceptance Criteria: The default intro chime must be normalized to -16 LUFS (Loudness Units relative to Full Scale).”

How INVEST supports agile processes like Scrum

The INVEST principles matter because they act as a compass for creating high-quality, actionable user stories that align with Agile goals and principles of processes like Scrum. By ensuring stories are Independent and Small, teams gain the scheduling flexibility needed to implement and release features in any order within short iterations. If user stories are not independent, it becomes hard to always select the highest value user stories. If they are not small, it becomes hard to select a Sprint Backlog that fit the team’s velocity.
Negotiable stories promote essential dialogue between developers and stakeholders, while Valuable ones ensure that every effort translates into a meaningful benefit for the user. Finally, stories that are Estimable and Testable provide the clarity required for accurate sprint planning and objective verification of the finished product. In Scrum and XP, user stories are estimated during the Planning activity.

FAQ on INVEST

How are Estimable and Testable different?

Estimable refers to the ability of developers to predict the size, cost, or time required to deliver a story. This attribute relies on the story being understood well enough and having a clear enough scope to put useful bounds on those guesses.

Testable means that a story can be verified through objective acceptance criteria. A story is considered testable if there is a definitive “Yes” or “No” answer to whether its objectives have been achieved.

In practice, these two are closely linked: if a story is not testable because it uses vague terms like “fast” or “high accuracy,” it becomes nearly impossible to estimate the actual effort needed to satisfy it. But that is not always the case.

Here are examples of user stories that isolate those specific violations of the INVEST criteria:

Violates Testable but not Estimable User Story: As a site administrator, I want the dashboard to feel snappy when I log in so that I don’t get frustrated with the interface.”

  • Why it violates Testable: Terms like “snappy” or “fast” are subjective. Without a specific metric (e.g., “loads in under 2 seconds”), there is no objective “Yes” or “No” answer to determine if the story is done.
  • Why it is still Estimable: A developer might still estimate this as a “small” task if they assume it just requires basic front-end optimization, even though they can’t formally verify the “snappy” feel.

Violates Estimable but not Testable User Story: As a safety officer, I want the system to automatically identify every pedestrian in this complex, low-light video feed.”

  • Why it violates Estimable: This is a “research project”. Because the technical implementation is unknown or highly innovative, developers cannot put useful bounds on the time or cost required to solve it.
  • Why it is still Testable: It is perfectly testable; you could poll 1,000 humans to verify if the software’s identifications match reality. The outcome is clear, but the effort to reach it is not.
  • What about Small? This user story is not small. It is a very large feature that takes a long time to implement.

How are Estimable and Small different?

While they are related, Estimable and Small focus on different dimensions of a user story’s readiness for development.

Estimable: Predictability of Effort

Estimable refers to the developers’ ability to provide a reasonable judgment regarding the size, cost, or time required to deliver a story.

  • Requirements: For a story to be estimable, it must be understood well enough and be stable enough that developers can put “useful bounds” on their guesses.
  • Barriers: A story may fail this criterion if developers lack domain knowledge, technical knowledge (requiring a “technical spike” to learn), or if the story is so large (an epic) that its complexity is hidden.
  • Goal: It ensures the Product Owner can prioritize stories by weighing their value against their cost.

Small: Manageability of Scope

Small refers to the physical magnitude of the work. A story should be a manageable chunk that can be completed within a single iteration or sprint.

  • Ideal Size: Most teams prefer stories that represent between half a day and two weeks of work.
  • Splitting: If a story is too big, it should be split into smaller, still-valuable “vertical slices” of functionality. However, a story shouldn’t be so small (like a “bag of leaves”) that it loses its meaningful context or value to the user.
  • Goal: Smaller stories provide more scheduling flexibility and help maintain momentum through continuous delivery.

Key Differences

  1. Nature of the Constraint: Small is a constraint on volume, while Estimable is a constraint on clarity.
  2. Accuracy vs. Size: While smaller stories tend to get more accurate estimates, a story can be small but still unestimatable. For example, a “Research Project” or investigative spike might involve a very small amount of work (reading one document), but because the outcome is unknown, it remains impossible to estimate the time required to actually solve the problem.
  3. Predictability vs. Flow: Estimability is necessary for planning (knowing what fits in a release), while Smallness is necessary for flow (ensuring work moves through the system without bottlenecks).

Should bug reports be user stories?

Mike Cohn explicitly advocates for this unified approach, stating that the best method is to consider each bug report its own story (Cohn 2004). If a bug is large and requires significant effort, it should be estimated, prioritized, and treated exactly like any other typical user story (Cohn 2004). However, treating every minor bug as an independent story can cause administrative bloat. For bugs that are small and quick to fix, Cohn suggests that teams combine them into one or more unified stories (Cohn 2004). On a physical task board, this is achieved by stapling several small bug cards together under a single “cover story card”, allowing the collection to be estimated and scheduled as a single unit of work (Cohn 2004).

From the Extreme Programming (XP) perspective, translating a bug report into a narrative user story addresses only the process layer; the technical reality is that a bug is a missing test. Kent Beck argues that problem reports must come with test cases demonstrating the problem in code (Beck and Andres 2004). When a developer encounters or is assigned a problem, their immediate action must be to write an automated unit or functional test that isolates the issue (Beck and Andres 2004). In this paradigm, a bug report is fundamentally an executable specification. Writing the story card is merely a placeholder; the true confirmation of the defect’s existence—and its subsequent resolution—is proven by a test that fails, and then passes (Beck and Andres 2004).

Applicability

User stories are ideal for iterative, customer-centric projects where requirements might change frequently.

Limitations

User stories can struggle to capture non-functional requirements like performance, security, or reliability, and they are generally considered insufficient for safety-critical systems like spacecraft or medical devices

User Stories in Practice

While user stories are widely adopted for building shared understanding (Patton 2014) and fostering a pleasant workplace among developers (Lucassen et al. 2016), empirical research highlights several significant challenges in their practical application.

Common Quality Issues

  • The NFR Blindspot: Practitioners systematically omit non-functional requirements (NFRs)—such as usability, security, and performance—because these constraints often do not fit neatly into the standard functional template (Lauesen and Kuhail 2022). Mike Cohn notes that forcing NFRs into the “As a… I want…” format often results in untestable statements like “The software must be easy to use” (Cohn 2004).
  • Rationale Hazard: While specified rationale (“so that…”) is essential for requirements quality (Lucassen et al. 2016), practitioners often fill this field in unjustifiably to satisfy templates. This forced inclusion of “filler” goals can directly lead to unverifiable requirements that obscure true business objectives (Lauesen and Kuhail 2022).
  • Ambiguity: Ambiguity manifests across lexical, syntactic, semantic, and pragmatic levels (Amna and Poels 2022). When analyzed collectively, vague stories often lead to severe cross-story defects, including logical conflicts and missing dependencies (Amna and Poels 2022).

Process Anti-Patterns

  • The “Template Zombie”: This occurs when a team allows its work to be driven by templates rather than the thought process necessary to deliver a product (Patton 2014). Practitioners become “Template Zombies” when they mechanically force technical tasks or backend services into the story format, often ignoring the necessary collaborative conversation (Patton 2014).
  • The Client-Vendor Anti-Pattern: Jeff Patton identifies a toxic dynamic where one party (often a business stakeholder) takes a “client” role to dictate requirements, while the other (often a developer or analyst) takes a “vendor” role to merely take orders and provide estimates. This creates a “requirements contract” that kills the collaborative problem-solving at the heart of agile development (Patton 2014).
  • Story Smells: Common “smells” include Goldplating (adding unplanned features), UI Detail Too Soon (constraining design before understanding goals), and Thinking Too Far Ahead (exhaustive detailing long before implementation) (Cohn 2004).

Automation and LLMs

Recent advancements in Large Language Models (LLMs) have introduced new capabilities for requirement engineering:

  • Syntactic Maturity: LLMs like GPT-4o excel at generating well-formed, atomic, and grammatically complete user stories, often outperforming novice analysts in following strict templates (Sharma and Tripathi 2025).
  • The Convergence Gap: While LLMs achieve high coverage of standard requirements, they exhibit a “convergence vs. creativity” trade-off. They tend to converge on predictable patterns and may miss novel or domain-specific nuances that human analysts provide (Quattrocchi et al. 2025).
  • The Power of Prompting: The quality of automated generation is highly sensitive to prompt design. Using a “Meta-Few-Shot” approach—combining structural rules with explicit positive and negative examples—can push LLM success rates significantly higher, even surpassing manual human generation in semantic accuracy (Santos et al. 2025).

Story Mapping and INVEST

The narrative flow of User Story Mapping captures the sequential and hierarchical relationships between stories (Patton 2014). From a theoretical perspective, this creates a notable tension with the INVEST criteria: while Story Mapping emphasizes the journey’s context and narrative flow, it can challenge the Independence criterion by highlighting the deep relationships between individual stories in a user journey. However, this mapping generally helps achieve the other INVEST criteria—particularly Valuable and Small—by providing a clear framework for slicing features into manageable releases.

Quiz

User Stories & INVEST Principle Flashcards

Test your knowledge on Agile user stories and the criteria for creating high-quality requirements!

What is the primary purpose of Acceptance Criteria in a user story?

What is the standard template for writing a User Story?

What does the acronym INVEST stand for?

What does ‘Independent’ mean in the INVEST principle?

Why must a user story be ‘Negotiable’?

What makes a user story ‘Estimable’?

Why is it crucial for a user story to be ‘Small’?

How do you ensure a user story is ‘Testable’?

What is the widely used format for writing Acceptance Criteria?

What is the difference between the main body of the User Story and Acceptance Criteria?

INVEST Criteria Violations Quiz

Test your ability to identify which of the INVEST principles are being violated in various Agile user stories, now including their associated Acceptance Criteria.

Read the following user story and its acceptance criteria: “As a customer, I want to pay for my items using a credit card, so that I can complete my purchase. (Note: This story cannot be implemented until the User Registration and Cart Management stories are fully completed).

Acceptance Criteria:

  • Given a user has items in their cart, when they enter valid credit card details and submit, then the payment is processed and an order confirmation is shown.
  • Given a user enters an expired credit card, when they submit, then the system displays an ‘invalid card’ error message.

Which INVEST criteria are violated? (Select all that apply)

Correct Answers:

Read the following user story and its acceptance criteria: “As a user, I want the application to be built using a React.js frontend, a Node.js backend, and a PostgreSQL database, so that I can view my profile.”

Acceptance Criteria:

  • Given a user is logged in, when they navigate to the profile route, then the React.js components mount and display their data.
  • Given a profile update occurs, when the form is submitted, then a REST API call is made to the Node.js server to update the PostgreSQL database.

Which INVEST criteria are violated? (Select all that apply)

Correct Answers:

Read the following user story and its acceptance criteria: “As a developer, I want to add a hidden ID column to the legacy database table that is never queried, displayed on the UI, or used by any background process, so that the table structure is updated.”

Acceptance Criteria:

  • Given the database migration script runs, when the legacy table is inspected, then a new integer column named ‘hidden_id’ exists.
  • Given the application is running, when any database operation occurs, then the ‘hidden_id’ column remains completely unused and unaffected.

Which INVEST criteria are violated? (Select all that apply)

Correct Answers:

Read the following user story and its acceptance criteria: “As a hospital administrator, I want a comprehensive software system that includes patient records, payroll, pharmacy inventory management, and staff scheduling, so that I can run the entire hospital effectively.”

Acceptance Criteria:

  • Given a doctor is logged in, when they search for a patient, then their full medical history is displayed.
  • Given it is the end of the month, when HR runs payroll, then all staff are paid accurately.
  • Given the pharmacy receives a shipment, when it is logged, then the inventory updates automatically.
  • Given a nursing manager opens the calendar, when they drag and drop shifts, then the schedule is saved and notifications are sent to staff.

Which INVEST criteria are violated? (Select all that apply)

Correct Answers:

Read the following user story and its acceptance criteria: “As a website visitor, I want the homepage to load blazing fast and look extremely modern, so that I have a pleasant browsing experience.”

Acceptance Criteria:

  • Given a user enters the website URL, when they press enter, then the page loads blazing fast.
  • Given the homepage renders, when the user looks at the UI, then the design feels extremely modern and pleasant.

Which INVEST criteria are violated? (Select all that apply)

Correct Answers:

References

  1. (Amna and Poels 2022): Asma Rafiq Amna and Geert Poels (2022) “Ambiguity in user stories: A systematic literature review,” Information and Software Technology, 145, p. 106824.
  2. (Beck and Andres 2004): Kent Beck and Cynthia Andres (2004) Extreme Programming Explained: Embrace Change. 2nd ed. Boston, MA: Addison-Wesley Professional.
  3. (Cohn 2004): Mike Cohn (2004) User Stories Applied: For Agile Software Development. Addison-Wesley Professional.
  4. (Lauesen and Kuhail 2022): Soren Lauesen and Mohammad A. Kuhail (2022) “User Story Quality in Practice: A Case Study,” Software, 1, pp. 223–241.
  5. (Lucassen et al. 2016): Gijs Lucassen, Fabiano Dalpiaz, Jan Martijn van der Werf, and Sjaak Brinkkemper (2016) “Improving agile requirements: the Quality User Story framework and tool,” Requirements Engineering, 21(3), pp. 383–403.
  6. (Patton 2014): Jeff Patton (2014) User Story Mapping: Better Software Through Community, Discovery, and Learning. O’Reilly Media.
  7. (Quattrocchi et al. 2025): Giovanni Quattrocchi, Liliana Pasquale, Paola Spoletini, and Luciano Baresi (2025) “Can LLMs Generate User Stories and Assess Their Quality?,” IEEE Transactions on Software Engineering.
  8. (Santos et al. 2025): Reine Santos, Gabriel Freitas, Igor Steinmacher, Tayana Conte, Ana Carolina Oran, and Bruno Gadelha (2025) “User Stories: Does ChatGPT Do It Better?,” International Conference on Enterprise Information Systems (ICEIS). SciTePress.
  9. (Sharma and Tripathi 2025): Amol Sharma and Anil Kumar Tripathi (2025) “Evaluating user story quality with LLMs: a comparative study,” Journal of Intelligent Information Systems, 63, pp. 1423–1451.
  10. (Wake 2003): Bill Wake (2003) “INVEST in Good Stories: The Series.”