blog heading image

Artificial intelligence has fundamentally changed the way that software is developed. Modern coding assistants can generate boilerplate code, explain unfamiliar APIs, and accelerate routine development tasks, allowing engineers to focus on solving more complex problems. However, this efficiency comes with a caveat. AI-generated code is not inherently correct, secure, or maintainable, and treating it as such introduces risk. This article examines the most common quality issues that AI introduces into codebases and outlines what engineering teams must do to prevent them.

Most AI coding assistants include a disclaimer that their output can contain errors and should be reviewed before use. This is a deliberate acknowledgment of the AI’s limits, and engineering teams should treat it accordingly. AI is best understood as a highly capable junior developer, not an infallible expert. Its output requires the same scrutiny, mentorship, and validation that a junior engineer's work would receive.

The sections below outline some of the most frequent categories of mistakes AI introduces, along with the review practices required to catch them.

Incorrect or Outdated Code

When prompted to generate code, AI is typically confident that the answer it gives is correct. However, it frequently calls methods that don’t exist, uses outdated APIs, and invents new configuration options. It also attempts to implement classes from older framework versions that have been deprecated instead of complying with the latest version of the framework. This has become common due to how rapidly technologies are evolving.

AI-generated code frequently appears correct during initial review while still containing functional or architectural issues. A developer should test the code to ensure it behaves correctly and should verify the generated code against official documentation instead of assuming that the best practices are being followed.

AI coding assistants frequently generate code similar to the following:

var client = new WebClient();
string json = client.DownloadString(url); 

However, WebClient is a legacy API and is not recommended for new development. AI might not initially catch this, and this code would need to be updated to use the more modern recommended code:  

private static readonly HttpClient Http = new();
string json = await Http.GetStringAsync(url); 

Security Vulnerabilities

When generating code, AI does not automatically prioritize security unless the developer prompts it to. It can introduce vulnerabilities for SQL injection, cross-site scripting (XSS), weak authentication, and secrets or API keys being hardcoded into the source code instead of in a more secure place, such as an environment variable.  

For example, in a C# project, AI could generate SQL SELECT statements such as the following:

// Vulnerable: string interpolation allows SQL injection 
string query = $"SELECT * FROM Users WHERE Username = '{username}'"; 

Although this implementation is functionally correct, it introduces a SQL injection vulnerability. Due to the username being included in the string, this line of code opens the application up to a vulnerability where malicious SQL could be injected into the place where the username should be filled in, putting the database at risk. To avoid this risk, it would be safer to write this code like this:

// Safer: parameterized query 
using var command = new SqlCommand( 
    "SELECT * FROM Users WHERE Username = @username", 
    connection); 
 
command.Parameters.Add("@username", SqlDbType.NVarChar, 256).Value = username; 

When necessary, the developer should instruct the AI agent to be mindful of these vulnerabilities, and they should review the generated code to ensure that it does not contain any risks.

Inefficient Algorithms

AI models frequently favor common algorithms rather than the most efficient algorithm for a specific use case. For example, it frequently uses nested loops that don’t scale, repeats database queries, and causes excessive memory allocations. The code typically works correctly in test environments, but when used in production with large data sets, the performance can degrade significantly.

For example, when trying to get a unique collection of items, AI might generate this code:

// Inefficient: O(n^2) because List.Contains is O(n) 
var result = new List<string>();   

foreach (var item in items) 
{ 
    if (!result.Contains(item)) 
    { 
        result.Add(item); 
    } 
} 

Although functionally correct, it may perform poorly when processing large amounts of data. If order does not matter, it would be more efficient to use the following code instead:

// More efficient: O(n) average using HashSet 
var uniqueItems = new HashSet<string>(items); 
var result = uniqueItems.ToList(); 

Overly Complex Code and Technical Debt

Similarly, AI can create code that is overengineered. For a task that should only take a few lines of code, AI frequently creates helper classes, interfaces, and builders. It may add interfaces and services that are not needed for a simple solution because it has seen examples from enterprise architectures where this level of abstraction is appropriate. AI-generated code frequently introduces technical debt by creating duplicate code, ignoring existing architecture, and adding unnecessary dependencies.

For example, if AI was asked to write code to format an email message, it might generate something similar to:

public interface IEmailFormatter 
{ 
    string FormatWelcomeEmail(string firstName); 
} 

public sealed class DefaultEmailFormatter : IEmailFormatter 
{ 
    public string FormatWelcomeEmail(string firstName) 
        => $"Welcome, {firstName}!"; 
} 

Meanwhile, the request could be fulfilled in one line:

string message = $"Welcome, {firstName}!";

While not always technically incorrect, AI-generated code can be overly complex, and as the codebase grows over time, it can become very difficult to maintain. AI prioritizes solving an immediate problem instead of considering that the code will need to be maintained over years. Developers should review every AI-generated implementation and question the AI on areas that can be simplified.

Ignoring Edge Cases and Error Handling

AI tends to follow the “happy path” when generating code, meaning that it does not always consider what could go wrong with the code and does not always add guardrails for them. It frequently overlooks null values, invalid user input, processing large files, etc. It also may not include retry logic, timeouts, logging, and user-friendly error messages.  

For example, AI commonly generates code such as the following to get the full name of a user:

// Happy path only: may throw NullReferenceException 
string fullName = user.Profile.FullName; 

This code will function correctly under normal operating conditions, and neither user nor user.Profile returns null. But once an error occurs, the application can fail or behave incorrectly due to these issues not being handled.

More checks, like in the code below, would be needed to catch the null values before this causes the application to misbehave. Developers still need to consider potential failure scenarios and instruct the AI to consider this.

// Contains null handling 
if (user?.Profile != null) 
{ 
    string fullName = user.Profile.FullName; 
} 

Lack of Business Context

When asked to complete a task, AI does not inherently understand the reason behind this request. It will complete the task in a way that is technically correct but may miss the nuance of the task because it doesn’t understand the business domain. This can result in code that does not match what the developer intended it to do.

These kinds of issues are not caught by the compiler and are unlikely to be caught by unit tests unless those tests thoroughly cover the business requirements. This requires review from a human who understands what the system is supposed to be doing. When prompting the AI, developers should provide detailed context so that the AI has enough detail that it needs to complete the task as desired.

Conclusion

AI-assisted development has become an important part of modern software engineering, but it does not eliminate the need for sound engineering practices. The responsibility for producing secure, maintainable, and correct software ultimately remains with the developer. Organizations that combine AI-assisted development with code reviews, testing, and architectural oversight can realize significant productivity gains without compromising code quality.  

By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.