When Software Works but Still Has Quality Problems: An Introduction to Performance Testing

Your code is in Roxoft green.

A web application can work correctly and still feel slow, unstable or unreliable. A user may be able to log in, search for data and complete a transaction without any functional errors, but if each action takes several seconds or the system starts failing when more users arrive, the application still has a quality problem.

In testing terms, these are two different questions. Functional testing checks whether the application does what it should - whether the right input produces the right output. Performance testing checks how well the application behaves under realistic conditions, including higher traffic, concurrent users and sustained load.

Both types of testing matter, but they reveal different problems.

What is performance testing?

Performance testing is a type of software testing used to evaluate the speed, stability and scalability of a system. In simple terms, it answers questions such as:

  • How quickly does the application respond?
  • How many users can it support at the same time?
  • What happens when traffic increases?
  • Does the system remain stable over time?
  • Where does performance start to degrade?

A performance test usually generates a defined amount of traffic and measures how the system responds. For example, a team may want to verify whether an application can support 500 concurrent users while keeping response times below a defined threshold.

The test itself, however, is only one part of the process. The more important part is understanding what the results mean. A system that works well with 100 users but starts returning errors at 300 users has revealed an important limitation. The purpose of testing is to discover that limitation before real users do.

Why functional testing is not enough

Functional testing checks whether the application behaves correctly. If a user enters valid credentials, they should be able to log in. If a form is submitted, the correct data should be saved. If a user performs a search, the expected results should appear.

These tests are essential, but they usually do not tell us how the system behaves under pressure. An API endpoint may return the correct response in 150 milliseconds during development, which looks perfectly acceptable. But what happens when hundreds of users call the same endpoint at the same time?

Response times may increase, database connections may become exhausted, CPU usage may rise and errors may start to appear. Functionally, the code may still be correct. The problem is that systems used by many users can behave very differently under concurrent load. That is where performance testing comes in.

Performance testing adds another layer of confidence by checking whether the application can continue behaving correctly when realistic traffic is introduced.

What do we actually measure?

Performance testing can produce a large number of metrics, and their relevance depends on the system and the goal of the test. However, three measurements provide a useful starting point because they describe different aspects of system behaviour.

Response time shows how long a request takes to complete. This is one of the most visible measurements because delays directly affect the user experience.

Throughput measures how much work the system can process over a given period. Depending on the application, this may mean requests per second, transactions per second or another relevant type of operation.

Error rate shows how many requests fail during the test. Fast response times mean very little if a significant percentage of requests are unsuccessful.

Response time, in particular, needs to be interpreted carefully. Looking only at the average can hide slower requests, which is why performance tests often use percentiles such as P95 or P99. A P95 response time of 600 milliseconds means that 95% of requests completed within 600 milliseconds, while the remaining 5% took longer. This provides a better view of how response times are distributed across requests than the average alone.

These test-level metrics tell us what is happening, but they do not necessarily explain why. Infrastructure and application metrics such as CPU usage, memory consumption, database activity and network usage provide that additional context. If response times begin to increase under load, monitoring these metrics can help identify the underlying bottleneck.

When should performance testing start?

Performance testing is often treated as something that happens near the end of a project, but that is usually too late. If a performance problem is caused by architecture, database design or the way services communicate, fixing it shortly before release can be expensive.

A better approach is to introduce performance testing gradually. Early tests can establish a baseline, while more realistic scenarios can be added as the application grows. Larger load tests still have their place before major releases, but performance should be measured long before that point.

This way, every new change can be checked against the baseline, and a drop in performance becomes visible soon after it is introduced.

The cost of skipping performance testing

Performance is part of software quality, not something that should be considered only when an application starts to feel slow. Users do not distinguish between a functional defect and a performance problem. If an action takes too long, repeatedly times out or fails when traffic increases, the result is the same: the application does not work as expected from their perspective.

The cost of discovering these problems also tends to increase as a system matures. A slow database query identified during development may require a relatively small change. The same issue discovered after release may already affect multiple services, production infrastructure, real users and business-critical workflows. At that point, identifying the root cause, implementing a fix, validating it and deploying it safely can become significantly more difficult.

This is similar to functional testing: problems are generally easier to address when they are detected close to the change that introduced them. Performance testing provides the same kind of early feedback on response times, capacity, scalability and system stability.

This does not mean that every application needs a large performance testing programme or thousands of simulated users. The scope should reflect the risks, expected traffic and complexity of the system. Even a small set of repeatable performance checks can establish a baseline and make regressions visible before they become production incidents.

Performance testing is therefore less about proving that a system is “fast” and more about reducing uncertainty. Teams gain evidence about how the application behaves under load, where its limits are and whether those limits are acceptable. The earlier that information is available, the more options a team has to act on it.