Developers often use the terms “linting” and “code review” loosely, sometimes interchangeably. This is understandable – both are quality practices, both examine code, and both produce feedback. But treating them as equivalent is a mistake that leads to a false sense of security. A codebase that passes all linting rules can still have critical design problems, logic errors, security vulnerabilities, and architectural inconsistencies. Linting and code review operate at fundamentally different levels, and understanding the gap between them is essential for building a real quality strategy.
What linting catches
A linter is a static analysis tool that checks code against a set of predefined rules. The rules are pattern-based: they match specific syntactic patterns and flag violations. Linters are fast, deterministic, and reliable within their scope. That scope is narrower than most people realise.
Formatting and style. Indentation, bracket placement, line length, trailing commas, semicolon usage. These are the rules that Prettier, ESLint's stylistic rules, and similar tools enforce. They ensure the code looks consistent, which reduces friction when multiple developers work on the same files.
Syntax errors and common mistakes. Unreachable code after a return statement. Variables declared but never used. Assignments in conditional expressions where a comparison was probably intended. Missing break statements in switch cases. These are mechanical errors that follow predictable patterns, and linters are excellent at catching them.
Simple anti-patterns. Using == instead of === in JavaScript. Catching exceptions without handling them. Using deprecated API methods. Re-declaring variables in a narrower scope. These are well-known mistakes with well-known rules to detect them.
Import and dependency issues. Some linters can check for circular imports, unused imports, or imports from disallowed modules. This is useful for enforcing module boundaries, though it only catches the structural violations, not the reasons behind them.
Everything a linter catches has one thing in common: it can be defined as a pattern match against the abstract syntax tree (AST) of the code. If the issue can be expressed as “whenever this syntactic pattern occurs, flag it,” a linter can handle it. If the issue requires understanding what the code means or how it fits into the broader system, a linter cannot.
What code review catches
Code review is a fundamentally different activity. Instead of matching patterns, a reviewer reads the code and evaluates it in context. The reviewer brings understanding of the project's goals, architecture, conventions, and constraints. This enables them to identify problems that no pattern-matching rule could express.
Design problems. A class that has taken on too many responsibilities. A module that couples two concerns that should be separate. An abstraction that leaks implementation details. A function that duplicates logic that exists elsewhere in the codebase. These are structural problems that require understanding the codebase as a system, not just the file in isolation.
Logic errors. An algorithm that produces incorrect results for certain inputs. A conditional that checks the wrong condition. A loop that terminates one iteration too early. A race condition between two concurrent operations. Logic errors are invisible to linters because the code is syntactically valid. It does something. It just does the wrong thing.
Architectural inconsistencies. One module uses callbacks while the rest of the codebase uses promises. The authentication module stores sessions differently from every other module. The new API endpoint does not follow the conventions established by the other thirty endpoints. These inconsistencies accumulate over time and increase the cognitive load for every developer who works on the codebase.
Business rule violations. The code implements a discount that should not apply to certain product categories. The access control check is missing for a user role that was added recently. The data retention logic does not account for GDPR requirements. A reviewer who understands the business context catches these. A linter has no concept of business rules.
Security vulnerabilities that depend on context. A linter might flag the use of eval() as unsafe, which is a simple pattern match. But a reviewer can identify that a particular database query is vulnerable to injection not because it uses a flagged function but because user input flows through three indirections before reaching the query without being sanitised anywhere along the way. This requires tracing data flow across functions and modules, which is beyond what pattern-based tools can do.
The gap between them
The gap between linting and code review is enormous, and it maps to the most consequential types of software defects.
Linting catches the issues that are easy to define and easy to fix: formatting, simple anti-patterns, mechanical errors. These are real issues, and catching them automatically is valuable. But they are rarely the issues that cause production incidents, security breaches, or multi-week refactoring efforts.
The issues that cause real damage are the ones that require context to detect: design decisions that create coupling, error handling strategies that leave gaps in observability, architectural patterns that diverge across the codebase, security oversights that span multiple modules. These are the domain of code review, and they are invisible to linters.
The danger is that teams mistake linting for review. When the CI pipeline runs ESLint and SonarQube and reports zero issues, there is a natural tendency to conclude that the code has been checked. It has been checked against a set of syntactic rules. It has not been evaluated as software. The difference matters.
Why running a linter does not mean your code has been reviewed
Consider a concrete example. A developer writes a new API endpoint that handles user profile updates. The linter checks the code and reports no issues: the formatting is correct, there are no unused variables, no deprecated functions, no suspicious patterns. The CI pipeline is green.
A reviewer looks at the same code and identifies five problems. First, the endpoint does not check whether the authenticated user is authorised to update the target profile – any logged-in user can update any other user's profile. Second, the endpoint accepts an email field but does not validate its format, which will cause downstream failures when the email processing service tries to parse it. Third, the error response includes the full database error message, which leaks internal schema details to the client. Fourth, the endpoint uses a different response format from every other endpoint in the API, which will confuse frontend developers. Fifth, the database query fetches the entire user record including the password hash, even though the endpoint only needs three fields.
None of these problems are linting issues. None of them violate a syntactic rule. All of them are significant. Three of them are security-relevant. And every one of them would have shipped to production if linting were the only quality gate.
Three tiers of code quality assurance
A useful way to think about code quality assurance is as three tiers, each operating at a different level of analysis.
Tier 1: Linting. Automated pattern matching. Catches formatting, style violations, simple anti-patterns, and mechanical errors. Fast, deterministic, cheap. Should run on every commit as part of the CI pipeline. The cost of not having linting is inconsistent code style and occasional mechanical bugs. The cost of relying only on linting is a false sense of security.
Tier 2: AI code review. Automated semantic analysis. An LLM reads the code and evaluates it against broader categories: security, architecture, error handling, consistency, performance. It can identify issues that require context, like inconsistent patterns across files, missing validation, or error handling gaps. It is not as deep as a human reviewer, but it covers categories that linting cannot touch and operates at a scale that human review cannot match.
Tier 3: Human code review. A developer with domain knowledge reads the code and evaluates it against the full context of the project: business requirements, team conventions, strategic architectural direction, and hard-won experience. Human review catches the subtlest issues – design decisions that will cause problems months from now, business logic that does not match the specification, architectural choices that conflict with the team's roadmap. It is the most expensive and slowest tier, but it catches things nothing else can.
Each tier catches issues the tier below cannot. Each tier is more expensive and slower than the tier below. The effective strategy is to use all three, not to pick one and assume it covers everything.
How the tiers work together
In practice, the three tiers should be layered.
Linting runs on every commit, automatically. It costs nothing in human time and catches the mechanical issues before they reach any reviewer. This means human and AI reviewers never have to waste time on formatting, unused variables, or simple anti-patterns. Their attention is freed for the issues that actually require it.
AI code review runs on demand or at key checkpoints – before a release, after a major feature, or periodically as a health check. It provides structured analysis across the entire codebase, flagging the architectural and security issues that linting ignores. The findings are specific, categorised, and prioritised, which makes them actionable without requiring interpretation.
Human code review happens on pull requests and at architectural decision points. With linting and AI review handling the mechanical and structural issues, human reviewers can focus on the highest-value questions: does this design make sense for our future plans, does this logic match the business requirements, and will this approach scale as the system grows.
Do not confuse the floor with the ceiling
Linting is the floor of code quality. It is the minimum standard that every professional codebase should meet. It ensures that the code is consistently formatted, free of obvious errors, and follows basic conventions. Every team should use it. No team should stop there.
The ceiling is genuine understanding of the code – its design, its security posture, its architectural coherence, its fitness for the business requirements it serves. Reaching that ceiling requires review: AI-powered analysis for breadth and consistency, human expertise for depth and judgement.
A clean linting report is a starting point, not a destination. It tells you the code is formatted. It does not tell you the code is good.
Limits and tradeoffs
- It can miss context. Treat findings as prompts for investigation, not verdicts.
- False positives happen. Plan a quick triage pass before you schedule work.
- Privacy depends on your model setup. If you use a cloud model, relevant code is sent to that provider; local models can keep inference on your own hardware.