<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Artem Korol</title>
    <description>The latest articles on Forem by Artem Korol (@artemtypes).</description>
    <link>https://forem.com/artemtypes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3188101%2Fb50a7f59-2b54-4763-8842-ca7a0e65893a.jpg</url>
      <title>Forem: Artem Korol</title>
      <link>https://forem.com/artemtypes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/artemtypes"/>
    <language>en</language>
    <item>
      <title>Internals of slices</title>
      <dc:creator>Artem Korol</dc:creator>
      <pubDate>Sat, 31 May 2025 20:28:09 +0000</pubDate>
      <link>https://forem.com/artemtypes/internals-of-slices-4fka</link>
      <guid>https://forem.com/artemtypes/internals-of-slices-4fka</guid>
      <description>&lt;h2&gt;
  
  
  🧠 Go Slices Internals: Why Your Slice Might Be Lying to You
&lt;/h2&gt;

&lt;p&gt;If you’ve ever changed one slice and watched another slice mysteriously change too… you’re not alone. Slices in Go are deceptively simple on the surface — but under the hood, they can trip up even seasoned developers.&lt;/p&gt;

&lt;p&gt;This post takes a deep look into how Go slices work internally — with visuals, examples, and real-world implications. Let’s go under the hood.&lt;/p&gt;

&lt;p&gt;🚧 &lt;strong&gt;TL;DR: What You’ll Learn&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How slices actually store data (and what that means for memory sharing)&lt;/li&gt;
&lt;li&gt;What &lt;code&gt;len&lt;/code&gt; and &lt;code&gt;cap&lt;/code&gt; really mean&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;append()&lt;/code&gt; allocates a new array (and when it doesn’t)&lt;/li&gt;
&lt;li&gt;Common bugs caused by misunderstood usage of slices  — and how to avoid them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔍 &lt;strong&gt;Slice Internals: What Is a Slice?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A slice in Go is &lt;em&gt;not&lt;/em&gt; an array. It’s a lightweight data structure that describes a &lt;strong&gt;portion of an array&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Under the hood, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;sliceHeader&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="kt"&gt;uintptr&lt;/span&gt; &lt;span class="c"&gt;// pointer to underlying array&lt;/span&gt;
    &lt;span class="n"&gt;Len&lt;/span&gt;  &lt;span class="kt"&gt;int&lt;/span&gt;     &lt;span class="c"&gt;// number of elements in the slice&lt;/span&gt;
    &lt;span class="n"&gt;Cap&lt;/span&gt;  &lt;span class="kt"&gt;int&lt;/span&gt;     &lt;span class="c"&gt;// capacity from start to end of backing array&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means &lt;strong&gt;multiple slices can point to the same underlying array.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🧠 &lt;strong&gt;Remember:&lt;/strong&gt; Slices are wrappers around arrays, not independent copies.&lt;/p&gt;

&lt;p&gt;➕ &lt;strong&gt;When Does append() Reallocate?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s look at another classic gotcha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// 🤔&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"b:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens?&lt;/p&gt;

&lt;p&gt;It depends on the &lt;strong&gt;capacity&lt;/strong&gt; of a. If there’s room, append reuses the backing array. If not, it allocates a new one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cap(a): %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// Let's say cap(a) == 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, &lt;code&gt;cap(b)&lt;/code&gt; is still 3, and appending a third element does not exceed the capacity, so the underlying array of &lt;code&gt;a&lt;/code&gt; slice is reused and updated too, which means result of two &lt;code&gt;Println&lt;/code&gt; functions would result in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Had you reached the capacity limit, Go would allocate a new array silently. This behaviour causes subtle bugs if you’re not paying attention.&lt;/p&gt;

&lt;p&gt;🧱 &lt;strong&gt;Making an Independent Copy&lt;/strong&gt;&lt;br&gt;
To avoid shared memory issues, always make a copy when you intend to work independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that b has its own backing array, which secures b slice of being accidentally modified. This is the best practice when your function accepts the slice as argument and modifies its content. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔍 Visual Summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s an ASCII diagram of what’s going on under the hood in the code snippet mentioned above, after &lt;code&gt;b&lt;/code&gt; becomes a sub-slice of the original slice &lt;code&gt;a&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Slice&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="o"&gt;^-----^&lt;/span&gt;   &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="nb"&gt;cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;Slice&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="o"&gt;^--^&lt;/span&gt;      &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="nb"&gt;cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Slice &lt;code&gt;b&lt;/code&gt; is a sub slice of &lt;code&gt;a&lt;/code&gt; which means it references original array, even though it does not have the same length.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Insight: you can actually access the slice &lt;code&gt;b&lt;/code&gt; elements at index &lt;code&gt;2&lt;/code&gt; and &lt;code&gt;3&lt;/code&gt; , however not by direct referencing via index, but as part of a sub-slice, like &lt;code&gt;b[:3]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🧯 &lt;strong&gt;Real Interview Story: Append Inside a Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s a deceptively simple example which perfectly illustrates how important is it to understand how slices work internally — and it’s one of the most common misunderstandings for newcomers to Go slices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// modifying slice with new values.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Preallocate space (len=0)(cap=4)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// populating values&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// [1 2 3]&lt;/span&gt;

    &lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c"&gt;// will it panic ?💥&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧠 &lt;strong&gt;What’s Happening?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you pass a slice to a function, Go &lt;strong&gt;copies the slice structure&lt;/strong&gt;. Inside mod function, go runtime checks the &lt;code&gt;len&lt;/code&gt; of the slice before appending a new value and compares it to &lt;code&gt;cap&lt;/code&gt; .  &lt;/p&gt;

&lt;p&gt;SInce &lt;code&gt;len&lt;/code&gt; value is less then &lt;code&gt;cap&lt;/code&gt; there is no need to allocate new array. New value is simply appended to the underlying array, while &lt;code&gt;Len&lt;/code&gt; field of the sliceHeader now equals &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;✨ &lt;strong&gt;But here is the catch.&lt;/strong&gt; ✨ &lt;/p&gt;

&lt;p&gt;Since array is an underlying structure of sliceHeader and no new allocation was mad, it keeps the value which we put in it in &lt;code&gt;mod&lt;/code&gt; function even after function returned. The only thing which didn’t change is &lt;code&gt;Len&lt;/code&gt; field of sliceHeader itself, since mod function operated on copy of the sliceHeader not original value 🙂.&lt;/p&gt;

&lt;p&gt;That means that when we return from &lt;code&gt;mod&lt;/code&gt; function, and call &lt;code&gt;len(s)&lt;/code&gt; it will return &lt;code&gt;3&lt;/code&gt; , but now we know why it happens, and that &lt;code&gt;s&lt;/code&gt; is actually . . .  of length &lt;code&gt;4&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Which means, when try to execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will not panic , but return slice with the value we put in it in &lt;code&gt;mod&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Can you feel, how good is it to understand these little details ? 🤌&lt;/p&gt;

&lt;p&gt;📌 &lt;strong&gt;Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Appending to a slice inside a function does mutate the original underlying array if it has enough space left.&lt;/strong&gt;&lt;br&gt;
This is a huge source of bugs if you don’t pay attention — and a classic Go interview question which got me with pants down 😅&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Final Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slices are wrappers (views) over an underlying array.&lt;/li&gt;
&lt;li&gt;Multiple slices can share the same underlying array.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;append()&lt;/code&gt; may or may not allocate a new array — it’s capacity-dependent.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;copy()&lt;/code&gt; if you need a truly independent slice or function must return modified version of passed slice.&lt;/li&gt;
&lt;li&gt;Be cautious when passing slices to functions. Slices are wrapping around arrays, which values still can be modified and lead to bugs 🪲&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>slices</category>
      <category>go101</category>
      <category>internals</category>
    </item>
  </channel>
</rss>
