Back in May 2023 (have we really had AI in our lives for that long!) I wrote an article comparing ChatGPT and Bard (now rebranded as Gemini) as coding assistants. Now, a couple of years later, I decided to revisit the free AI offerings from OpenAI and Google to see how they measure up.

The original task back in May 2023 was:

Challenge 1: Converting jQuery to Native JS

At the time, this was something I was doing quite a lot. Since then, jQuery has phased out even more. I’ve barely touched jQuery in the past couple of years, but for the sake of comparison, I’ll redo the test.

Me: “Please can you convert the following Jquery in native Javascript:”
function scrollWin(element_id,speed){
	$('html,body').animate({
		scrollTop: $("#"+element_id).offset().top
	}, speed);
}

Gemini

To convert the provided jQuery function to native JavaScript, you’ll need to use standard DOM methods and a custom animation loop. The jQuery function `scrollWin` smoothly scrolls the page to a specific element.
function scrollWin(elementId, speed) {
  const element = document.getElementById(elementId);
  if (!element) return; // Exit if the element doesn't exist

  const targetPosition = element.offsetTop;
  const startPosition = window.pageYOffset;
  const distance = targetPosition - startPosition;
  let startTime = null;

  function animation(currentTime) {
    if (startTime === null) startTime = currentTime;
    const timeElapsed = currentTime - startTime;
    const run = ease(timeElapsed, startPosition, distance, speed);
    window.scrollTo(0, run);
    if (timeElapsed < speed) requestAnimationFrame(animation);
  }

  // This is a simple easing function for a smooth scroll.
  // It's a basic ease-out sine function.
  function ease(t, b, c, d) {
    t /= d / 2;
    if (t < 1) return c / 2 * t * t + b;
    return -c / 2 * ((--t) * (t - 2) - 1) + b;
  }

  requestAnimationFrame(animation);
}

Q. Does it work?
A. Yes, when added to a test.html page, the function smoothly scrolls down the screen.

Q. How does it compare with the 2023 answer?
A. This answer is certainly longer and more complicated. The answer from 2023 used window.scrollTo({ top: topPosition, behavior: 'smooth' }), which is actually a simpler and more 'modern' approach. When I questioned this with Gemini, it replied that it's approach was more robust and would support older browsers. Although it did conceed that for most modern web projects, the original solution is actually preferable. It's cleaner, more readable, and leverages a native browser feature for a highly optimized and performant animation.

Gemini: "In summary, my code is better for maximum compatibility and customization, while your code is better for simplicity and performance in modern browsers."

Verdict: The solution worked, but I think perhaps Gemini over complicated things a little bit and I would have preferred the simpler, more elegant option. If I hadn't known better I may have used a much more complicated solution than was necessary.


ChatGPT

ChatGPT's response was more thorough. It gave me 2 options; the simpler modern method and the manual animation approach.

Here’s an equivalent native JavaScript version of your jQuery function:
Option 1: Smooth Scrolling (Modern Approach)

If you just need smooth scrolling (most browsers support it):

function scrollWin(element_id, speed) {
    const target = document.getElementById(element_id);
    if (!target) return;

    // Native smooth scrolling (ignores `speed`, browser handles timing)
    window.scrollTo({
        top: target.offsetTop,
        behavior: 'smooth'
    });
}

✅ Pros: Clean and uses built-in smooth scrolling
❌ Cons: speed parameter is not used (the browser controls the duration)

Option 2: Custom Speed (Manual Animation)

If you want to replicate jQuery’s speed (e.g. 400ms):

function scrollWin(element_id, speed) {
    const target = document.getElementById(element_id);
    if (!target) return;

    const start = window.pageYOffset;
    const end = target.offsetTop;
    const distance = end - start;
    const startTime = performance.now();

    function animate(time) {
        const elapsed = time - startTime;
        const progress = Math.min(elapsed / speed, 1); // clamp between 0 and 1
        window.scrollTo(0, start + distance * progress);
        if (progress < 1) requestAnimationFrame(animate);
    }

    requestAnimationFrame(animate);
}

✅ Pros: Lets you control speed just like jQuery
✅ Cons: Slightly more code


Q. Do they work?
A. Yes, both solutions worked when added to a test page.

Q. How does it compare with the 2023 answer?
A. Last time ChatGPT suggested a similar approach to Gemini this time. However this answer offered both approaches, so is by far the most useful answer.

Verdict: ChatGPT showed me a couple of solutions, pointed out the pros and cons of both and allowed me to chose the one suited for my project. This level of transparency is really appreciated, so for me, ChatGPT gave a better response for this task.


Challenge 2: Writing new Javascript code for tabbed content

Me: “Please can you write me some javascript/html/css which has tabs for content?”

Gemini

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tabbed Content Interface</title>
    <!-- Tailwind CSS CDN for easy styling -->
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #f3f4f6;
        }
        /* Custom styling for active tab border */
        .tab-button.active {
            border-bottom-width: 2px;
            border-color: #4f46e5;
        }
    </style>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center p-4">

    <!-- Tab Container -->
    <div class="bg-white rounded-xl shadow-lg w-full max-w-2xl overflow-hidden">
        
        <!-- Tab Buttons Container -->
        <div class="flex border-b border-gray-200">
            <button class="tab-button w-1/3 py-4 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none transition-colors duration-200 active" data-tab-target="#tab1">
                About
            </button>
            <button class="tab-button w-1/3 py-4 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none transition-colors duration-200" data-tab-target="#tab2">
                Features
            </button>
            <button class="tab-button w-1/3 py-4 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none transition-colors duration-200" data-tab-target="#tab3">
                Pricing
            </button>
        </div>

        <!-- Tab Content Container -->
        <div class="p-6">
            <!-- Tab 1 Content -->
            <div id="tab1" class="tab-content">
                <h2 class="text-xl font-semibold text-gray-800 mb-2">About Our Service</h2>
                <p class="text-gray-600">
                    Welcome to our service! We are dedicated to providing the best solutions for all your needs. Our team is passionate about delivering high-quality products and excellent customer support. We strive for innovation and continuous improvement to ensure you have the best experience possible.
                </p>
            </div>

            <!-- Tab 2 Content -->
            <div id="tab2" class="tab-content hidden">
                <h2 class="text-xl font-semibold text-gray-800 mb-2">Key Features</h2>
                <ul class="list-disc list-inside space-y-2 text-gray-600">
                    <li>Responsive design for all devices.</li>
                    <li>Intuitive and easy-to-use interface.</li>
                    <li>Fast and reliable performance.</li>
                    <li>Secure data handling and privacy protection.</li>
                    <li>24/7 customer support.</li>
                </ul>
            </div>

            <!-- Tab 3 Content -->
            <div id="tab3" class="tab-content hidden">
                <h2 class="text-xl font-semibold text-gray-800 mb-2">Pricing Plans</h2>
                <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
                    <div class="bg-gray-50 rounded-lg p-4 text-center">
                        <p class="text-sm font-medium text-gray-500">Starter</p>
                        <p class="text-3xl font-bold text-gray-900 mt-2">$9<span class="text-lg font-normal text-gray-500">/mo</span></p>
                    </div>
                    <div class="bg-gray-50 rounded-lg p-4 text-center">
                        <p class="text-sm font-medium text-gray-500">Pro</p>
                        <p class="text-3xl font-bold text-gray-900 mt-2">$29<span class="text-lg font-normal text-gray-500">/mo</span></p>
                    </div>
                    <div class="bg-gray-50 rounded-lg p-4 text-center">
                        <p class="text-sm font-medium text-gray-500">Enterprise</p>
                        <p class="text-3xl font-bold text-gray-900 mt-2">$99<span class="text-lg font-normal text-gray-500">/mo</span></p>
                    </div>
                </div>
            </div>
        </div>

    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // Get all tab buttons and content sections
            const tabButtons = document.querySelectorAll('.tab-button');
            const tabContents = document.querySelectorAll('.tab-content');

            // Add a click event listener to each button
            tabButtons.forEach(button => {
                button.addEventListener('click', () => {
                    // Get the target content ID from the data-tab-target attribute
                    const targetId = button.dataset.tabTarget;

                    // Deactivate all tabs and hide all content sections
                    tabButtons.forEach(btn => btn.classList.remove('active'));
                    tabContents.forEach(content => content.classList.add('hidden'));

                    // Activate the clicked button and show its corresponding content
                    button.classList.add('active');
                    document.querySelector(targetId).classList.remove('hidden');
                });
            });
        });
    </script>
</body>
</html>
This code provides a clean, fully-contained HTML file. The CSS is handled by Tailwind for a modern look, and the JavaScript is a simple event listener that shows and hides content based on which tab is clicked. The initial state is set up with the "About" tab active.

Q. Does it work?
A. Yes, the code generated works beautifully. Gemini chose to use Tailwind CSS for the example, I'm not sure if this is because I've used Tailwind in the majority of my projects lately and it just knows my preference, but for me atleast, this is a great solution.


ChatGPT

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tabbed Content Example</title>
<style>
  body {
    font-family: Arial, sans-serif;
    background: #f5f5f5;
    margin: 0;
    padding: 40px;
  }

  .tabs {
    max-width: 600px;
    margin: auto;
    background: #fff;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 4px 10px rgba(0,0,0,0.1);
  }

  /* Tab Buttons */
  .tab-buttons {
    display: flex;
    background: #eee;
  }

  .tab-buttons button {
    flex: 1;
    padding: 14px;
    background: #eee;
    border: none;
    cursor: pointer;
    font-size: 16px;
    transition: background 0.3s;
  }

  .tab-buttons button:hover {
    background: #ddd;
  }

  .tab-buttons button.active {
    background: #fff;
    font-weight: bold;
    border-bottom: 2px solid #3498db;
  }

  /* Tab Content */
  .tab-content {
    display: none;
    padding: 20px;
    animation: fade 0.3s ease-in-out;
  }

  .tab-content.active {
    display: block;
  }

  @keyframes fade {
    from {opacity: 0;}
    to {opacity: 1;}
  }
</style>
</head>
<body>

<div class="tabs">
  <div class="tab-buttons">
    <button class="tab-link active" data-tab="tab1">Tab One</button>
    <button class="tab-link" data-tab="tab2">Tab Two</button>
    <button class="tab-link" data-tab="tab3">Tab Three</button>
  </div>

  <div id="tab1" class="tab-content active">
    <h2>Tab One</h2>
    <p>This is the content for Tab One. You can place any HTML here, including images, text, or forms.</p>
  </div>

  <div id="tab2" class="tab-content">
    <h2>Tab Two</h2>
    <p>This is the content for Tab Two. Use it to display different information without leaving the page.</p>
  </div>

  <div id="tab3" class="tab-content">
    <h2>Tab Three</h2>
    <p>This is the content for Tab Three. Tabs are great for organizing related content.</p>
  </div>
</div>

<script>
document.querySelectorAll('.tab-link').forEach(button => {
  button.addEventListener('click', () => {
    const tabId = button.dataset.tab;

    // Remove active state from all buttons
    document.querySelectorAll('.tab-link').forEach(btn =>
      btn.classList.remove('active')
    );

    // Hide all tab contents
    document.querySelectorAll('.tab-content').forEach(content =>
      content.classList.remove('active')
    );

    // Activate clicked button and its content
    button.classList.add('active');
    document.getElementById(tabId).classList.add('active');
  });
});
</script>

</body>
</html>

Q. Does it work?
A. Yes, again the code generated works beautifully. No reliance on any third party CSS here, just plain, stand-alone HTML/CSS/JS.

ChatGPT also provided detailed instructions on how it works, alongside further customisation tips. So another great response.

Verdict: For my purposes, I think I actually prefer Gemini's response here. I nearly always use Tailwind CSS these days, so providing a solution that uses Tailwind suits me perfectly. Interestingly, both solutions, visually looked very similar. But using Tailwind gave the Gemini response the slight edge.

The other advantage (although sometimes it's a distinct disadvantage) of using Gemini, is the code is loaded into the 'canvas' so you can often preview things right there in the browser. Whilst this is great when it works, I do actually find the 'canvas' slightly irritating at times. Often the code I'm working with is just a snippet of a blade template for example, which can't be rendered on it's own, so having a broken version showing in 'canvas' is not particularly helpful. So for this reason I actually find code provided by ChatGPT can sometimes be easier to work with. But on this occasion, 'canvas' worked great. It's also hard to view past code examples when using Gemini, where as ChatGPT just has everything in one long thread, so you just have to scroll back up to see it.

So which is better for coding in 2025? ChatGPT or Gemini?

Gemini Pros:

  • You can have long, uninterrupted conversations and you're not prompted to 'upgrade' in order to continue chatting.
  • When it works, the canvas can be very handy to preview code in the browser.
  • You get very long, thorough, responses. Whereas ChatGPT feels like it's trying to save on money: limiting input and output to as few characters as it can get away with, Gemini just throws out endless reams of code. So you get more 'copy and pastable' code than ChatGPT. It'll formulate it's answers in complete code blocks, rather than leaving it up to you to piece together the samples.

Gemini Cons:

  • Some of the code produced is maybe not the best quality. It'll give you a bunch of code, but it's up to you to make sure you apply some developer QA to make sure it's not complete garbage.
  • The canvas can be a complete pain in the neck when it doesn't render properly. Getting past code samples can then be tricky once the canvas updates, and you need to ask Gemini to repeat itself.

ChatGPT Pros:

  • I feel the code quality is perhaps slightly better than Gemini. I have more confidence in the answers provided. I also always use ChatGPT for copyrighting as I find the tone is excellent and it has a far better understanding of good writing.
  • I like the fact you can just scroll up the chat to view past code samples - unlike Gemini's canvas which updates and you lose past code.

ChatGPT Cons:

  • You typically get much less comprehensive answers than Gemini. Often you'll get a response that's been abbreviated in some way, which can make incorporating the response much more time consuming than just getting a working code sample.
  • You'll be asked to upgrade if your conversation goes on too long. Which is fair enough, I appreciate they're offering this for free, but it can be frustrating to have to re-start the same conversation from scratch. Gemini doesn't have the same limitation.

This is actually not as straight forward to answer as it was in 2023. Gemini has come a long way and, for all intents and purposes, caught up with ChatGPT in terms of code generation - at least for the projects I've worked on. I use both ChatGPT and Gemini extensively these days, sometimes cross referencing answers between bots when the one seems to be going around in circles on a problem. They both have their pros and cons. For copywriting it's ChatGPT every time. If I have a clear requirement for some functionality and I just want the code, then Gemini is a great code companion.

I'm able to say, for example, as I did recently when developing the FAQ schema generator for aiprofiles.co.uk:

"I’m developing a Laravel app where users can create profiles with structured schema.org markup. I'd like to create a new signup flow, where users can provide their name, email, and website URL. The system should then use the website content to automatically generate frequently asked questions and answers, which can then be added to their profile in a format suitable for schema markup Please can you help me with this?"

By providing a much more detailed description of how I wanted it to work, technically, Gemini and I were able to collaborate seamlessly. It saved me an enormous amount of time, generating all the component parts—migrations, routes, jobs, Blade templates, and more - everything needed to build a fully functional feature. I was able to develop a new feature to the site in roughly a tenth of the time it would have taken me otherwise, and I genuinely think the code it produced is very solid.

If you're an experienced developer, using AI for coding supercharges your productivity. It's breathtaking what you can produce in a fraction of the time it would have taken previously. If you're inexperienced, you can also do wondrous things with AI, but you run the risk of: a) going around in circles when you're presented with a solution that's never going to work and not having the experience to realise the AI is taking you down the garden path, and b) producing some scarily complicated code spaghetti.

I'd love to hear if anyone has any other good coding platforms out there that they use, and why they prefer them to Chatty or Gemini?