System Design Unleashed series #2: Functional and Non-functional requirements

The Learning Game
12 min readApr 4, 2024

At the foundations of a system design sit its business and performance requirements. Failing to understand these will lead to a plethora of problems down the line, all of which converge into higher costs (businesses will lose money by missing dates when they wanted to ship the product to the market, more money needs to be spent for prolonged software development, the system can be full of bugs and consequently detract customers from it, it can become an unmaintainable legacy, etc.). Requirements play a crucial role in defining what a system should do and how the system should behave, essentially helping everyone involved (developers, designers, managers, clients, etc.) by aligning their expectations and ensuring that the product meets the desired objectives.

Requirement gathering is the first step toward hitting the target.

Throughout the history of software development, simple coding errors have often caused major failures that resulted in significant financial losses. For example, in 1962, a missing hyphen in the code led to the destruction of The Mariner Spacecraft. Similarly, in 1988, a mistake in the algorithm inadvertently created the first computer virus known as The Morris Worm, among other instances. The industry has progressed to catch such simple errors well before they reach the production state, for example, by implementing proper testing strategies. Of course, this is not to say that the code errors don’t reach the end customers — just look at how frequently application updates occur on your phone. However, it happens rarely today that such coding errors cause that kind of damage and/or loss of money.

Nowadays, most often organizations lose money or their products fail because they haven’t properly thought them through or managed the product from the beginning — honestly, talk to any friend you have and ask them about the teams they are working on, or the clients they are working with, and how much time they are wasting by working on product features that will bring no real additional value, or they disagree with completely. The answer shouldn’t surprise you, and it should give you a fair amount of laughter.

A proper software lifecycle and the processes that follow should start with everyone involved truly understanding what is it that you are trying to build. Not understanding fundamental questions affects everyone — the development teams will have a tough time working on an unmaintainable solution, users will hate the product because the app takes forever to load, the security team will have to patch issues live, and the business team won’t be happy when they realize that the product they paid sucks. It is often said that writing the code is the easier part while solving the problem is the main battle.

So, what are the Functional and Non-Functional requirements?

To understand what the Functional and Non-Functional requirements are, let us use an example of a laptop with Windows installed on it, and a MacBook with their native operating system (note: I am not saying that one is better than the other, so don’t bite my head off because of it, I am just trying to give a proper example here).

Windows vs. Mac

You can charge both laptops and depending on the manufacturer, you can replace any dying component. When you turn them on, you can install the same or depending on the OS similar applications that essentially do the same. You can use the browser to search the web, log in to your favorite social media website, watch YouTube, play games, or create documents and presentations. Both provide you, the user, with the same function. Whether you choose one over the other comes from a personal preference. So, the basic functions of a system are called, you guessed it — the functional requirements.

Now, let us focus for a moment on the build quality and the system's performance. Apple has been famous throughout history for the fact that its products work well with one another, and are considered fast, reliable, and secure. Is it the same if you install a Windows OS on a $300 machine? Of course, you can buy a Lenovo ThinkPad for $3000, install Windows on it, and it will run pretty smoothly, but even then all that is needed is one bad driver that causes a camera to misbehave, and you — as a user, will start pulling your hair off. All aspects of a product that affect the product’s quality and the overall user experience are the non-functional requirements, often called - the quality attributes.

A couple of software examples

To fully grasp what we mean by the Functional and Non-functional requirements, let us move away from the never-ending battle of Apple vs. Windows, and focus on a couple of other famous software companies and their products.

Examples

Viber or WhatsApp:

The functional requirements for a messaging app such as Viber or WhatsApp would be the functions of the system, e.g. the users should be able to send encrypted messages; the users should be able to send photos; the users should be able to call and video call each other; when users want to send the messages the system should try to map their phonebook with the list of registered users on the Viber/WhatsApp service and offer them to contact only registered users; users should be able to back up their conversations on cloud or local storage, etc.

The non-functional requirements or quality/performance attributes for messaging apps such as Viber or WhatsApp could be some of the following: The message encryption algorithm needs to work within 2 milliseconds; the message needs to be delivered to the person on the other side within 1 second after pressing the send button; when user wants to send photos the system need to downsize the photos to the lower quality of a specific size so that the transfer over the network happens over 60 seconds; if the user is connected to the network, backup of the conversations should happen daily and will not last more than 1 hour; if the nearest data center goes down because of natural disaster, messages should be routed through the next nearest data center without introducing more than 2 seconds of delay; application must be available 99.999% of the time; app has to support 100k users in parallel, etc.

Uber or Bolt:

The functional requirements for riding apps such as Uber or Bolt could be: user should be able to request the ride only from cities where the app has coverage; users shouldn’t be able to request the ride if they haven’t added and confirmed their credit card; drivers should be able to receive a list of requested rides based on their geographical proximity; drivers should be able to set working hours; drivers should be able to select if they are currently working or not, etc.

The non-functional requirements for riding apps such as Uber or Bolt could include: if they are eligible for a drive, drivers should receive a ride request within 1 second of its submission; if the ride is accepted, users should receive a confirmation within 1 second; when the driver location is streamed to an end-user cell phone, the request to update location shouldn’t exceed 1 kb in size; application has to support 1k of concurrent requests, etc.

The comparison table

In a nutshell:

  • Functional requirements outline what the system should do. They cover all the functions that the software must perform to meet the needs and expectations of its users; they are (usually) easy to specify; and are tested first (mostly through API testing, integration testing, end-to-end testing, etc.). Functional requirements never dictate the software architecture, they just focus on the software functions.
  • Non-functional requirements are concerned with the system’s quality and performance attributes; cover all aspects of good user experience; ensure that users’ needs are satisfied; are (often) difficult to specify; and are tested after functional testing. They do not focus on what the system does, but on how the system performs for a specific performance dimension — and the two most important characteristics are that the requirements have to be measurable and testable. They cover things such as availability, scalability, performance, etc. There is a saying that without a non-functional requirement, the system is like a house of cards ready to go down with a slight breeze.

Btw #1, it is worth mentioning that different sub-types of the functional requirements exist, which will not be covered in this blog post (I might update it at one point to hold this information as well — but I feel there are more important topics to focus on).

Btw #2, project/program managers like to categorize requirements into business requirements, stakeholder requirements, and solution requirements (functional and non-functional). I will skip this discussion for the same reason as in the previous paragraph. For software system design, it is more than enough to remain at the level of functional and non-functional.

Btw #3, non-functional requirements are often called quality attributes (although, if we are nitpicking there is a slight difference between quality attributes and non-functional requirements — quality attributes represent the characteristics of the system while non-functional requirements represent the concrete metric we want to hit for those characteristics), they are also called *ilities or *itys as some of the common non-functional requirements are performance, availability, scalability, maintainability, reliability, etc.

Btw #4, if you Google non-functional requirements and go to Wikipedia, you will see an extensive list of things such as accessibility, adaptability, auditability, availability, and on and on. There are many non-functional requirements, and no single architecture can meet all of them (some are even contradictory to one another). Of course, when designing the system, we shouldn’t try to cover as much as we can, either. The usual approach is to choose the non-functional requirements we are interested in and focus only on those — and there are a couple of usual suspects every system should worry about. We will cover these and more in great detail later in the series.

Gathering the Functional requirements

The naive way of gathering the requirements is simply by asking the client to describe what are all the things they want the system to do. If the system is complex and has multiple actors, this is a recipe for failure, as it is a sure way to overlook many important things. Also, sometimes even the clients are not 100% sure what they want to build.

A better approach to gathering the functional requirements is through use cases and user flows. Use cases portray situations or scenarios in which the system is used. Its sibling user flow depicts a more detailed, step-by-step, graphical use case representation.

The algorithm:

  1. Identify all actors of our system (users).
  2. List all use cases (scenarios).
  3. Create a user flow for each use case (user flows are most commonly represented by using the sequence diagrams).

To demystify the topic, and understand it more clearly, let us jump into an example of a riding app such as Uber.

Step 1: Identify all the actors

Who would use an app such as Uber? Well, on one side we have riders that need to get from point A to point B, and on the other side, we have the drivers who are using the app to earn money.

Step 2: List all use cases

Of course, in the example below, we are not going to list all use cases, but you’ll get the gist of it.

To use the app, the rider needs to be registered, the driver needs to be registered, the rider needs to be logged in, the driver needs to be logged in, the rider can request a ride and the driver can accept, the rider can request a ride and they can get “no available vehicles” response, etc.

Step 3: Create user flow for each use case

For explanation, let us choose one use case and create a user flow around it — let us pick the most complex one where the user/rider requests a ride and the driver accepts it.

As mentioned, a popular way of creating user flows is through sequence diagrams. Sequence diagrams are a concept from the Unified Modeling Language (UML), and although UMLs are not strictly followed in the industry, sequence diagrams are often used to represent interactions between entities.

A couple of notes on how to read the sequence diagram, the time flows from top to bottom, arrows represent requests, and dashed or broken arrows represent responses. When the system is active, there is a gray box, and when the system is actively performing an action, there is an additional rectangle with a curved arrow around it. It’s pretty simple, you’ll get a hang of it.

Ride-sharing sequence diagram

The actual sequence diagram can, in addition to interactions between actors also hold the data that is being transferred (in other words, request and response structure).

… and just like that, if you do this step for every use-case or scenario you identified, you are already building a good foundation for the system design.

Gathering the Non-functional requirements

Ok, so we know from the business standpoint what we need to build, we identified actors; we thought about what scenarios we want to cover and created sequence diagrams for those. Now we can focus on the system’s performance, but where do we start?

History teaches us that software is being rewritten not because we failed to understand what the functions of the system are (although that was the case back in the good old Waterfall days), but because the system is not fast enough, the system became a legacy which is hard to develop or maintain; it is hard to find developers to work on outdated tech stack; it is not secure enough, or the system can’t scale well with the growing number of users. Hence the reason why nailing the step of gathering non-functional requirements is of the utmost importance.

A popular way of gathering non-functional requirements is by going through a well-designed NFR (non-functional requirements) checklist. Many companies have created templates for this purpose, and software architects or engineering managers need to fill them in before embarking on a new project. This ensures that they take into consideration important performance aspects while designing the architecture. You can find examples of such checklists on the web: Example 1, Example 2.

Have in mind that when working with NFRs, the following aspects need to be taken into consideration:

  • They have to be measurable and testable; as you will need to follow the progress on each metric during the software development life cycle.
  • No architecture can help you achieve all of them, and some NFRs you focus on will contradict one another, so prioritization is the obvious next step.
  • Some NFR requests won’t be feasible at all because of the high cost, so be realistic about what can be accomplished with the current budget.

Before diving into system design, the last piece you need to consider is the subtype of non-functional requirements known as system constraints.

System constraints

A system constraint is essentially a decision that was fully or partially already made for us, restricting our freedom in designing the system.

We often consider system constraints as pillars of software architecture because these decisions are made for us. They serve as a starting point, and we need to build the architecture around them, as they are non-negotiable most of the time.

Constraints broadly fall into one of three categories: (1) business, (2) technical, or (3) legal.

When designing architecture, sacrifices are commonly made because of business constraints. E.g. if a budget is severely limited, or the timeline is pretty aggressive, we’ll have to take that into account when designing a system. Also, having to integrate with a SaaS (Software as a Service) company and use their system to handle payments affects the design.

If you're not starting a new project from scratch, there's a good chance you'll encounter technical constraints such as being tied to a specific cloud provider, programming language, database, library, or framework, having to support different operating systems or browsers, etc.

Last but not least, while you can usually negotiate around business and technical constraints, the non-negotiable part is tied to regulations, which are the legal constraints. E.g. GDPR (General Data Protection Regulation) in the European Union dictates how long you can store data, while HIPAA (The Health Insurance Portability and Accountability Act) limits access to medical data.

That wraps up our discussion on Functional and Non-functional requirements.

This blog is part of the two ongoing series:

  1. System Design Unleashed series: Mastering the Art of Effective Solutions, and
  2. Engineering Management Unleashed series: Playbook for Achieving Excellence

--

--