Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
150693 stories
·
33 followers

Preventing Resource Leaks in Go: How GoLand Helps You Write Safer Code

1 Share

Every Go application uses resources, such as files, network connections, HTTP responses, and database query results. But when a resource is left unclosed, it can cause a leak, which drains memory, exhausts system limits, introduces subtle bugs, and eventually brings even the most robust service to failure. Recently, we’ve introduced resource leak analysis in GoLand to address this problem and help you detect unclosed resources before they cause leaks in production.

What is a resource leak?

A resource is any entity that holds an external system handle or state that must be explicitly closed when it’s no longer needed. In Go, such types typically implement the io.Closer interface, which defines a single Close() method for cleaning up underlying resources.

Here are some common implementations of io.Closer:

  • *os.File: an open file descriptor obtained via functions like os.Open and os.Create.
  • net.Conn: a network connection (TCP, UDP, etc.) created by net.Dial, net.Listen.Accept, or similar functions.
  • *http.Response: the response object returned by http.Get, http.Post, and similar functions. Its Body field is a resource because it implements io.ReadCloser.
  • *sql.Rows and *sql.Conn: database query results and connections.

A resource leak occurs when one of these resources isn’t properly closed after use. In such cases, they continue to occupy memory and other limited system resources such as file descriptors, network sockets, or database connections. Over time, all the open resources may lead to performance degradation, instability, or even application failure.

The more you know…
Can’t Go’s garbage collector handle this? After all, it automatically frees unused memory, so why not resources, too?
Go’s garbage collector (GC) is designed specifically to reclaim memory, not to manage external resources like open files or network connections. In some rare cases, it can help. For example, in the standard library, a finalizer can be set to call Close() when a file becomes unreachable. However, this technique is used only as a last resort to protect developers from resource leaks, and you can’t rely on it completely.
Garbage collection is non-deterministic. It might occur seconds or minutes later, or not at all, leading to system limits being reached. That’s why the only safe and reliable way to avoid leaks is to explicitly close every file or connection as soon as you’re done with it.

Tips to prevent resource leaks

What can you do to prevent resource leaks in Go applications? A few consistent habits can help you minimize them.

Tip 1: Use defer to close resources

The defer statement ensures that cleanup happens even if a function returns early or panics. As shown in the example below, we close the created resource right after successfully opening it using defer f.Close(), which is one of the simplest and most effective ways to avoid resource leaks.

f, err := os.Open("data.txt")
if err != nil {
    return err
}
defer f.Close()

Since the defer statement executes after the surrounding function’s return, you must handle errors first and only then defer the resource closure. This ensures that you don’t defer operations on invalid or uninitialized resources. Also, avoid placing defer inside loops that create many resources in succession, as this can lead to excessive memory usage.

Tip 2: Test your application under load

Most resource leaks affect your application only under high load, so it’s a good idea to run load and stress tests to see how it behaves. For example, if you’re developing a backend service, you can use load-testing tools like k6, wrk, Vegeta, or others. This testing helps you uncover not only potential resource leaks but also performance bottlenecks and scalability issues before they affect your users.

Tip 3: Use static code analysis tools

Static analysis tools can also help you automatically detect unclosed resources in your code. For instance, golangci-lint includes linters such as bodyclose and sqlclosecheck, which track HTTP response bodies and SQL-related resources to ensure you haven’t forgotten to close them.

Resource leak analysis in GoLand 2025.3 takes this a step further. It scans your code as you write it, verifies that resources are properly closed across all execution paths, and highlights any potential issues in real time. Getting this feedback right in your IDE helps you catch leaks early. Moreover, it works with any type that implements io.Closer, including your custom resource implementations.

When one missing Close() breaks everything

Are resource leaks really that critical? Let’s take a look at two common cases to see how a single missing Close() call can cause serious issues or even break your application over time.

Case 1: Leaking HTTP response bodies

Sending HTTP requests is a common practice in Go, often used to fetch data from external services. Suppose we have a small function that pings an HTTP server:

func ping(url string) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    return resp.Status, nil
}

When you write code like this, GoLand warns you about a potential resource leak because of an unclosed response body. But why does it do that? We aren’t even using the body in this example!

Well, let’s try running this code and see what happens. To simulate high-load conditions, we can call our function in a loop like this:

var total int
for {
   _, err := ping("http://127.0.0.1:8080/health")
   if err != nil {
      slog.Error(err.Error())
      continue
   }

   total++
   if total%500 == 0 {
      slog.Info("500 requests processed", "total", total)
   }
   time.Sleep(10 * time.Millisecond)
}

At first glance, everything seems fine – the client sends requests and receives responses as expected. However, if you monitor memory usage, you’ll notice that it gradually increases with every request the program sends, which is a clear indicator that something is wrong:

After some time, the client becomes completely unable to send new requests, and the logs start filling up with errors like this:

Why does this happen? When you make an HTTP request in Go, the client sets up a TCP connection and processes the server’s response. The headers are read automatically, but the body remains open until you close it.

Even if you don’t read or use the body, Go’s HTTP client keeps the connection alive, waiting for you to signal that you’re finished with it. If you don’t close it, the TCP connection stays open and cannot be reused. Over time, as more requests are sent, these unclosed connections accumulate, leading to resource leaks and eventually exhausting the available system resources.

That’s exactly what happens in our example. Each iteration of ping() leaves an open response body behind, memory usage grows steadily, and after a while, the client can no longer open new connections, resulting in the can’t assign requested address error.

Let’s correct the mistake and run the code again:

func ping(url string) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close() // Important: close the response body

    return resp.Status, nil
}

After applying this fix, the memory footprint remains minimal, and you’ll no longer see the previous errors.

It’s worth mentioning that the code snippet above uses Go’s default http.Client for simplicity, which is generally not recommended in production. By default, it has no request timeout, so a slow or unresponsive server can cause requests to hang indefinitely, potentially leading to stalled goroutines and resource exhaustion. A better practice is to create a custom http.Client with reasonable timeouts to keep your application responsive and resilient under poor network conditions.

Case 2: Forgetting to close SQL rows

Another common and destructive source of leaks in Go applications involves database resources, particularly when using the standard database/sql package. Let’s take a look at a simple function that retrieves user names by country from a database:

func (s *Store) GetUserNamesByCountry(country string) ([]string, error) {
    rows, err := s.db.Query(`SELECT name FROM users WHERE country = $1`, country)
    if err != nil {
        return nil, err
    }

    var names []string
    for rows.Next() {
        var name string
        if err := rows.Scan(&name); err != nil {
            return nil, err
        }
        names = append(names, name)
    }

    rows.Close()

    return names, rows.Err()
}

Even though we call rows.Close() explicitly, GoLand still warns us about a possible leak. But why?

Let’s say our table looks something like this:

Have you spotted the problem? That’s right, there’s a user without a name. Мore precisely, there is a NULL instead of a string, and the GetUserNamesByCountry function doesn’t handle such cases properly. When rows.Scan tries to assign a NULL to a Go string variable, it returns an error. Issues like this can happen to anyone, and that nameless user probably ended up in our table by mistake. Still, it may seem that such a small issue couldn’t cause any dramatic consequences. After all, that’s exactly why we tried to handle errors properly, right?

Let’s simulate real conditions by calling the function with different input parameters in a loop:

var total int
for {
    for _, country := range countries {
        _, err := s.GetUserNamesByCountry(country)
        if err != nil {
            slog.Error(err.Error())
            continue
        }

        total++
        if total%100 == 0 {
            slog.Info("100 queries processed", "total", total)
        }
        time.Sleep(10 * time.Millisecond)
    }
}

When we launch the program, everything seems fine, and we only get occasional errors as expected:

However, after running and processing SQL queries for some time, our program completely breaks down, and the logs are full of errors like this:

We’ve run out of client connections, and our application is unable to retrieve any data from the database!

The issue is that one of the execution paths in GetUserNamesByCountry leaves the query result unclosed when an error occurs during scanning. If rows is not closed, the corresponding database connection remains in use. Over time, this reduces the number of available connections, and eventually, the connection pool becomes exhausted. That’s exactly what happened in our case.

Surprisingly, just one incorrect row in our table is enough to take down the entire application, simply because we forgot to close the resource properly.

The best way to prevent this mistake is to use defer. As we discussed previously, this should be your preferred way of handling resources whenever possible:

func (s *Store) GetUserNamesByCountry(country string) ([]string, error) {
    rows, err := s.db.Query(`SELECT name FROM users WHERE country = $1`, country)
    if err != nil {
        return nil, err
    }
    defer rows.Close() // Important: close the rows using 'defer'

    var names []string
    for rows.Next() {
        var name string
        if err := rows.Scan(&name); err != nil {
            return nil, err
        }
        names = append(names, name)
    }

    return names, rows.Err()
}

Making the invisible visible

There are many ways a resource leak can creep into your program, and the examples above are just the tip of the iceberg. Such mistakes are easy to make and hard to trace. Your code compiles, tests pass, and everything appears to work, until your service slows down or starts failing under load. Finding the root cause can be time-consuming, especially in large codebases.

GoLand’s resource leak analysis makes these issues visible right where they start, as you write code. It tracks how resources are used across all execution paths and warns you if something might remain unclosed. This early feedback helps you quickly identify resources in your code and fix potential leaks right away.

The feature is especially helpful for beginners who are still learning the language and might not yet know which resources require explicit cleanup. Experienced developers benefit as well, since it saves time when working with unfamiliar codebases and custom types that implement io.Closer.

In essence, resource leak analysis turns a subtle, hard-to-detect problem into something you can catch instantly, helping you write more reliable and maintainable Go code.

Keeping your Go applications leak-free

Resource leaks are among the most subtle yet destructive bugs in Go applications. They rarely cause immediate crashes, but over time, they can silently degrade performance, create instability, and even bring down production environments.

By using defer consistently, testing under realistic load, and taking advantage of GoLand’s new resource leak analysis, you can catch these issues early and keep your applications stable and reliable. Try out the new feature in the latest GoLand release and let us know what you think!

Read the whole story
alvinashcraft
39 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Apriel-1.6-15b-Thinker: Cost-efficient Frontier Multimodal Performance

1 Share
Read the whole story
alvinashcraft
40 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Introducing a fresh new look for comments in Word for the web

1 Share

Hi, Insiders! I’m Tobi Solarin, a Product Manager on the Word team. I’m excited to share our newly updated design for comments in Word for the web.

Introducing a fresh new look for comments in Word for the web

Many of you shared that the previous commenting experience felt outdated. We’ve listened and introduced a cleaner, more modern design featuring refreshed icons, buttons, and an improved layout.

The new design also helps you focus on what matters most. Selected comments are highlighted, while unselected ones fade into a subtle gray, making it easier to direct your attention when navigating feedback.

How it works

  1. Open a new or existing document in Word for the web.
  2. Highlight text and select New Comment, and notice that your comments and replies have a new look!

Availability

This update is rolling out to all Word for the web users.

Feedback

We’d love to hear your feedback! To share your thoughts, select Help > Feedback in Word, and include #NewCommentUX in your responses.

 

Learn about the Microsoft 365 Insider program and sign up for the Microsoft 365 Insider newsletter to get the latest information about Insider features in your inbox once a month!

Read the whole story
alvinashcraft
40 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Uno Platform Studio 2.0 - Deep Dive & $10k Contest

1 Share


Read the whole story
alvinashcraft
40 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Jaime’s build context: A Flutter developer’s thoughts about Antigravity + Tips!

1 Share

Hi, I’m Jaime Wren, a long-time developer tooling software engineer on the Flutter team. I’ve seen many shifts in the software industry over the years, but the whole industry has just stepped into a new Wild West at breakneck speed and this is both exciting and nerve-wracking.

In my opinion, we developers are complicated creatures driven by two forces: the need for productivity with our tasks and our inherent joy of programming itself. We thrive when we can slip into a fast, uninterrupted loop. To realize our full potential as developers, we need our entire toolchain to come together without introducing friction. As we transition into the Agentic Era, this need for a cohesive loop is non-negotiable; this is where Flutter has a distinct advantage.

I’ve tested a lot of AI tools these last few years, waiting for something that actually will save Flutter developers time rather than just shifting where time is spent. Nothing turned the corner for me until Antigravity, Google’s new Agentic IDE. It bridges the gap between raw model capability and actual engineering utility. I can finally see all of the infrastructure and tooling around the LLMs coming together to remove friction, rather than adding to it.

From my own testing, Flutter isn’t just “compatible” with AI tools like Antigravity; it is uniquely equipped to power them. Why? It comes down to Flutter’s strict structures and robust tooling. A core philosophy of Antigravity is a reliance on verification to know if a piece of code actually works. Flutter’s tools provide the immediate feedback that Antigravity’s agents need to validate these actions. All it takes to get started are a few settings to configure extensions and the MCP server.

Some cool things you can do

There are several cool things that I and other members of the Flutter community have learned to do with Antigravity to speed our development process. I’ve listed a few of them below, but I request that if you have any additional tips, please add them in the comments attached to this post.

Run tests & fix the problematic code

You can use Antigravity to run existing tests, fix any problematic code that is breaking your tests, and then verify that the tests are passing again. Here is the prompt that I use:

Execute the full test suite with `flutter test`. If failures occur,
fix them one by one. After applying each fix, re-run the full suite one
last time to ensure no regressions.

Fix errors and warnings

To prepare a PR before pushing it, you can use Antigravity to fix your errors, warnings and lints. Here is my prompt for this:

Run `flutter analyze` over my project. If it fails, fix any errors and 
warnings, then validate that they are fixed.

Discover lints and use them

There are a lot of lints out there but I don’t have time to research them all and figure out which ones are best for each project. I discovered that this cool prompt helps Antigravity quickly scan my project, suggest, and enable lints that make sense for it:

Read https://dart.dev/tools/linter-rules and identify rules I am
implicitly following but haven't enabled. Add them to analysis_options.yaml,
run flutter analyze, and fix any resulting violations.

Discover good pub.dev packages for my project

There are tons of pub.dev packages out there, and like the lints, researching them takes time and effort. I wanted a way to figure out which ones might be best for my projects while not leaving my IDE. This prompt worked well for me:

I need preferences to be shared from one instance of my app to the next,
please go search for this feature in https://pub.dev/, then run
`flutter pub get`, validate that the pub status is in a good status.
Then, find an example in my project to use this new package,
fix any issues that appear from `flutter analyze`, and fix them.
Finally, add a test for the new feature.

Thoughts about my journey with Flutter over the years and why I’m excited

I remember when Flutter entered the scene about 10 years ago. From the beginning and in my opinion, Flutter brought together the right language, framework and tools to be usable-by-default. An often overlooked factor in Flutter’s success is that these pieces weren’t bolted together from different vendors. Instead, the framework, rendering engine, CLI tooling, IDE tooling, language and ecosystem have always been driven by the same organization that has enabled these teams to coordinate to enable the usable-by-default cohesive vision. It’s because of this that I believe there is a “I finally get Hot Reload” moment for every new Flutter developer that makes them excited and hopeful because:

  • The Flutter infrastructure is capable of patching code in milliseconds.
  • The errors and warnings appear as you type and ensure that code is valid before a save occurs.
  • The language helps prevent syntax errors and frictions.
  • Obvious overflow errors are displayed as yellow and black stripes that signal layout issues that need to be addressed.

In the past year, the promise of generative AI in software development has been akin to the Wild West–demos and products have shown a lot of progress, but have not followed the usable-by-default mantra that has been core to Flutter’s success. With model improvements such as larger context windows that allow a model to actually read a project, and with concepts in the space converging, frameworks like Flutter have had an entry point to add value to agents and workflows. The Flutter team followed suit this year with the GenUI effort, the Dart and Flutter MCP Server, and AI rules suggestions.

Now, with Antigravity entering the arena, we have something that is more than a chatbot built into a window in your IDE. Antigravity inhabits the IDE, running commands and using knowledge about other Flutter and Dart projects to correctly follow your direction and take actions on your behalf in the project space.

As Flutter engineers, we take for granted the ability to know how to immediately run static analysis (dart analyze) or launch an app (flutter run) in a project we’ve never seen before. While none of these restrictions have limited what developers could create with Flutter, the natural consequence with LLMs and agentic tooling is that our collective uniformity gives these interfaces a huge leap forward in understanding and acting on the structure of our projects.

To reap the benefits of LLM tooling, agents require a verification through a positive feedback loop. If a model is producing value on every tenth piece of output and hallucinating the other nine instances, I might be amused, but I won’t be using the tool again. With Antigravity, verification is a core philosophy designed to give agents a feedback loop, allowing them to iterate before finishing work. This is where the benefit of existing robust Flutter tools comes into play, when using Antigravity to assist in writing code, the agent literally iterates until analysis is clean, formatting is consistent, tests pass, and the output from `flutter run` confirms that pixels are being drawn.

In summary

Without a doubt, these new AI tools are redefining how we identify as productive developers who enjoy the practice of programming. Equally without doubt, a robust, fast, uninterrupted loop will continue to be the centerpiece of that experience.

Antigravity doesn’t replace the programmer; it removes the drudgery that stands between your idea and reality. The loop is getting faster, the ramp is already built, and the future of Flutter is more usable — and more joyful — than ever.


Jaime’s build context: A Flutter developer’s thoughts about Antigravity + Tips! was originally published in Flutter on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read the whole story
alvinashcraft
40 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

LangGrant Announces MCP Server

1 Share
Read the whole story
alvinashcraft
40 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories