How AI can streamline code reviews

Imagine having a pairing buddy to help you with those finicky Pull Request (PR) reviews. During reviews, when it comes to refining small code snippets, Large Language Models (LLM) using Artificial Intelligence (AI) is my new friend and pairing buddy. Using AI, developers can quickly offer improvement suggestions based on identified optimisation opportunities. In this blog post, I’d like to share a few examples from my recent PR reviews, where AI not only pinpointed precise code improvements but also provided insightful explanations on how these changes improve the readability and performance of the code. When I say “AI” in this post, I’m referring to text generating models like ChatGPT, Claude AI, or DeepSeek.

It is essential to understand that AI does not review the PRs. Instead, it is used as a tool whose primary function is to assist me with the reviews including:

  • Informative comments: AI helps to write thoughtful, contextual comments that facilitate constructive dialogue about the code.
  • Code improvements suggestions: Alongside review comments, AI suggests code changes that could enhance the overall quality.

Let’s dive into the examples straight away.

Example 1: Optimising using Map data structure

In this example, the suspicion was an “m x n” array traversal on category.products which was inefficient. I suggested an optimisation by avoiding the “m x n” traversal using a hash map for faster lookups. AI confirmed the approach and also provided back the refactored code as output.

Code changes before and after refactor

export const getSelectedServerTagAssignments = (
  tagAssignments: SelectedTagAssignments[],
  defaultTagAssignments: TagSectionType[],
) => {
-  const tagAssignmentsArray = tagAssignments.map((assignment) => ({
-    id: assignment.productId,
-    quantity: assignment.quantity,
-  }))
+  const tagAssignmentsMap = new Map(
+  tagAssignments.map((assignment) => [assignment.productId, assignment]),
+  )

  return defaultTagAssignments.map((category) => ({
    ...category,
    products: category.products.map((product) => {
-      const matchingAssignment = tagAssignmentsArray.find(
-        (assignment) => assignment.id === product.id,
-      )
+      const matchingAssignment = tagAssignmentsMap.get(product.id)
      const baseProduct = {
        ...product,
        quantity: matchingAssignment ? matchingAssignment.quantity : 0,
      }
      if (!category.allowDuplicateSelections) {
        return {
          ...baseProduct,
          isSelected: !!matchingAssignment,
        }
      }
      return baseProduct
    }),
  }))
}

Prompt to AI

// Code snippet before refactor

Are the array iterations in this function implemented optimally? Can we reduce any iterations and determine the return value in a better way? Also any other ways to improve the readability of this function?

PR Review comment

Disclaimer: This comment was inspired by AI based on a hunch that the nested iteration could be avoided.

This is what happens in the current version of the function:

  • Multiple Iterations: The function creates a tagAssignmentsArray by mapping over tagAssignments, and then it iterates over defaultTagAssignments with a nested iteration over category.products. This results in multiple iterations.
  • Redundant Property Mapping: Some properties are being mapped and re-mapped unnecessarily.
  • Readability: The nested structure can be hard to read and understand. I think we can rewrite this function to use a Map data structure tagAssignmentsMap instead of tagAssignmentsArray which is performant.
// Code snippet after refactor

I did not know Map data structure existed in JS/TS and was a learning for me. All the tests pass after the above change. What do you think?

Example 2: Simplify for readability

When reviewing this example, it was clear at first glance that the new conditional check had introduced a redundant dispatch call, and the code could benefit from some clean-up. While drafting a comment to point this out would have been straightforward, AI stepped in and instantly provided the elegant refactored code. It was a small but satisfying win for efficiency.

Code changes before and after refactor

   useEffect(() => {
     if (doesItemTagWithSections) {
+      let sections = product?.sections
       if (serverItemDetail && serverItemId) {
+        sections = getSelectedServerTagAssignments(
-        const newTagAssignments = getSelectedServerTagAssignments(
           serverItemDetail.tagAssignments,
           tagSections,
         )
-        dispatch(
-          copyTagSectionsAsync({
-            lineId: tagLineItem,
-            sections: newTagAssignments,
-          }),
-        )
-      } else {
-        dispatch(
-          copyTagSectionsAsync({
-            lineId: tagLineItem,
-            sections: product?.sections,
-          }),
-        )
       }
+      dispatch(
+        copyTagSectionsAsync({
+          lineId: tagLineItem,
+          sections,
+        }),
+      )
     }
   }, [product?.sections])

Prompt to AI

// Code snippet before refactor

Can we refactor this such that we call dispatch only once?

PR Review comment

// Code snippet after refactor

Does this simplify the code? In this effect, instead of calling the dispatch twice, we get the arguments conditionally and then call dispatch once.

Example 3: Unique keys instead of index

Passing the array index as a key to a React component is a common developer mistake. While it might seem convenient, it can lead to subtle bugs. AI helped me articulate this clearly as a PR review comment.

Code changes before refactor

<FlatList 
  data={currentData} 
  keyExtractor={(item, index) => `${item.favoriteName}-${index}`}
  ...
/>

Prompt to AI

// Code snippet above

Explain why it’s not a good idea to use index as keys in keyExtractor in 50 words or less.

PR Review comment

Would it be good to have an id in the fake data and use that as a key here? If the list is reordered, filtered, or items are added/removed, the index of each item will change. This can cause React to mistakenly think that the same item is a different one, leading to unnecessary re-renders or incorrect behaviour. It’s generally not a good idea to use index for key and also name is not guaranteed to be unique.

Example 4: Simplify for readability

The code in this example felt verbose and hard to understand at first glance. These are the instances where you don’t need to involve AI even though it’s handy. Did I ask AI or not? :)

Code changes before refactor

const hasTagAssignments = (orderItem: OrderLineType): boolean =>
  (orderItem.tagAssignments.length ?? 0) > 0

PR Review comment

const hasTagAssignments = (orderItem: OrderLineType): boolean =>
  orderItem.tagAssignments?.length > 0

Does this condition achieve the outcome you hope and more readable?

Conclusion

In the past, my PR reviews and feedback might have been less detailed with only suggestions making the developer guess the implementation details or pair program with me to implement the same. But with AI, I’ve started to provide detailed feedback with code suggestions that can be pasted immediately reducing friction. As a result, I’ve received a lot of positive feedback from developers recently making my review efforts worthwhile and rewarding.

Finally, it’s essential to rely on intuition and use AI as a tool to assist in the review process. If AI played a significant role in crafting a specific comment, please disclose this by including a disclaimer. The final review comment should focus on selecting the most useful insights from the AI’s responses and presenting them in a natural, human-like manner, rather than copying the entire output returned.

Throughout this blog post, I’ve been referencing AI, and you may be wondering if it’s ChatGPT. Despite ChatGPT’s popularity, I’ve been using different LLMs for my code reviews. I’ll write more about this and share my development workflow in a future post. Stay tuned!