Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

Learning Go: Stale slices explained

October 18, 2023
Massimiliano Ziccardi
Related topics:
GoLinux
Related products:
Red Hat Enterprise Linux

    This article focuses on some weird Go slice behaviors that the developer might encounter without adequate knowledge of slice internals.

    I will begin by illustrating the problem with a fictional company called ACME Corporation and its software engineer, Wile E. Then I will explain why that happened and how to avoid it.

    Wile meets the Go slices

    During the company's day of learning, Wile decides to learn the Go language. He read about arrays and slices and wants to do some practice, beginning with a straightforward exercise—a piece of code that:

    • creates a slice containing all the digits from 1 to 0
    • derives from the previous one, another slice containing all the digits from 1 to 9.

    Wile thinks: "Wow, that's really easy! Let's try some code!"

    He starts writing the code and ends up with the first exercise:

    package main
    
    import "fmt"
    
    func main() {
    	// STEP1: create the first slice with the digit from 1 to 0
    	fmt.Println("[STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)")
    	charSlice1 := []byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
    	fmt.Println("[STEP1]", "charSlice1", string(charSlice1))
    
    	fmt.Println("\n[STEP2] Create a slice containing all digits from the first slice but the last one")
    	// STEP2: create a new slice with all the digits from slice 1 but the last one (the 0)
    	charSlice2 := charSlice1[:len(charSlice1)-1]
    	fmt.Println("[STEP2]", "charSlice2", string(charSlice2))
    
    	fmt.Println("\nFinal Result:")
    	// Print both slices
    	fmt.Println("charSlice1", string(charSlice1))
    	fmt.Println("charSlice2", string(charSlice2))
    }
    

    He runs the code and gets the following output:

    [STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)
    [STEP1] charSlice1 1234567890
    
    [STEP2] Create a slice containing all digits from the first slice but the last one
    [STEP2] charSlice2 123456789
    
    Final Result:
    charSlice1 1234567890
    charSlice2 123456789

    Wile thinks: "Well, that's been easy! From what I learned, a slice is just a view on a backing array, so charSlice1 and charSlice2 are pointing to the same backing array. Let's try to change the content of one: if that's true, the other one will also change!"

    Wile ends up with the following code:

    package main
    
    import "fmt"
    
    func main() {
    	// STEP1: create the first slice with the digit from 1 to 0
    	fmt.Println("[STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)")
    	charSlice1 := []byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
    	fmt.Println("[STEP1]", "charSlice1", string(charSlice1))
    
    	// STEP2: create a new slice with all the digits from slice 1 but the last one (the 0)
    	fmt.Println("\n[STEP2] Create a slice containing all digits from the first slice but the last one")
    	charSlice2 := charSlice1[:len(charSlice1)-1]
    	fmt.Println("[STEP2]", "charSlice2", string(charSlice2))
    
    	// STEP3: change the numbers 3 and 4 with an X in charSlice2
    	fmt.Println("\n[STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2")
    	charSlice2[2] = 'X'
    	charSlice2[3] = 'X'
    	fmt.Println("[STEP3]", "charSlice2", string(charSlice2))
    
    	// Print both slices
    	fmt.Println("\nFinal Result:")
    	fmt.Println("charSlice1", string(charSlice1))
    	fmt.Println("charSlice2", string(charSlice2))
    }
    

    He runs it and gets the following output:

    [STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)
    [STEP1] charSlice1 1234567890
    
    [STEP2] Create a slice containing all digits from the first slice but the last one
    [STEP2] charSlice2 123456789
    
    [STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2
    [STEP3] charSlice2 12XX56789
    
    Final Result:
    charSlice1 12XX567890
    charSlice2 12XX56789

    As expected, changing charSlice2 changed charSlice1 too.

    Wile thinks: "That's too easy. I'm starting to love Go! Now I want to add a fourth step that appends a value to the slices."

    After a few seconds, Wile ends up with the snippet below:

    package main
    
    import "fmt"
    
    func main() {
    	// STEP1: create the first slice with the digit from 1 to 0
    	fmt.Println("[STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)")
    	charSlice1 := []byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
    
    	// STEP2: create a new slice with all the digits from slice 1 but the last one (the 0)
    	fmt.Println("\n[STEP2] Create a slice containing all digits from the first slice but the last one")
    	charSlice2 := charSlice1[:len(charSlice1)-1]
    
    	fmt.Println("\n[STEP2] Result:")
    	fmt.Println("[STEP2]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP2]", "charSlice2", string(charSlice2))
    
    	// STEP3: change the numbers 3 and 4 with an X in charSlice2
    	fmt.Println("\n[STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2")
    	charSlice2[2] = 'X'
    	charSlice2[3] = 'X'
    
    	fmt.Println("\n[STEP3] Result:")
    	fmt.Println("[STEP3]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP3]", "charSlice2", string(charSlice2))
    
    	// STEP4: append a '-' to charSlice2
    	fmt.Println("\n[STEP4] Append '-' to charSlice2")
    	charSlice2 = append(charSlice2, '-')
    
    	fmt.Println("\n[STEP4] Result:")
    	fmt.Println("[STEP4]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP4]", "charSlice2", string(charSlice2))
    }
    

    However, this time, running it doesn't show what Wile was expecting:

    [STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)
    
    [STEP2] Create a slice containing all digits from the first slice but the last one
    
    [STEP2] Result:
    [STEP2] charSlice1 1234567890
    [STEP2] charSlice2 123456789
    
    [STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2
    
    [STEP3] Result:
    [STEP3] charSlice1 12XX567890
    [STEP3] charSlice2 12XX56789
    
    [STEP4] Append '-' to charSlice2
    
    [STEP4] Result:
    [STEP4] charSlice1 12XX56789-
    [STEP4] charSlice2 12XX56789-

    "In Step 4, why does appending a character in charSlice2 change the last character of charSlice1?" Wile wonders. "Weird..."

    He is a bit confused, so he tries to add a further step (Step 5) that adds three more characters to charSlice2.

    He writes the following code he wrote:

    package main
    
    import "fmt"
    
    func main() {
    	// STEP1: create the first slice with the digit from 1 to 0
    	fmt.Println("[STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)")
    	charSlice1 := []byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
    
    	// STEP2: create a new slice with all the digits from slice 1 but the last one (the 0)
    	fmt.Println("\n[STEP2] Create a slice containing all digits from the first slice but the last one")
    	charSlice2 := charSlice1[:len(charSlice1)-1]
    
    	fmt.Println("\n[STEP2] Result:")
    	fmt.Println("[STEP2]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP2]", "charSlice2", string(charSlice2))
    
    	// STEP3: change the numbers 3 and 4 with an X in charSlice2
    	fmt.Println("\n[STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2")
    	charSlice2[2] = 'X'
    	charSlice2[3] = 'X'
    
    	fmt.Println("\n[STEP3] Result:")
    	fmt.Println("[STEP3]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP3]", "charSlice2", string(charSlice2))
    
    	// STEP4: append a '-' to charSlice2
    	fmt.Println("\n[STEP4] Append '-' to charSlice2")
    	charSlice2 = append(charSlice2, '-')
    
    	fmt.Println("\n[STEP4] Result:")
    	fmt.Println("[STEP4]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP4]", "charSlice2", string(charSlice2))
    
    	// STEP5: append '+*/' to charSlice2
    	fmt.Println("\n[STEP5] Append '+*/' to charSlice2")
    	charSlice2 = append(charSlice2, '+', '*', '/')
    
    	fmt.Println("\n[STEP5] Result:")
    	fmt.Println("[STEP5]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP5]", "charSlice2", string(charSlice2))
    
    }
    

    Another surprise hits Wile. Now the output is:

    [STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)
    
    [STEP2] Create a slice containing all digits from the first slice but the last one
    
    [STEP2] Result:
    [STEP2] charSlice1 1234567890
    [STEP2] charSlice2 123456789
    
    [STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2
    
    [STEP3] Result:
    [STEP3] charSlice1 12XX567890
    [STEP3] charSlice2 12XX56789
    
    [STEP4] Append '-' to charSlice2
    
    [STEP4] Result:
    [STEP4] charSlice1 12XX56789-
    [STEP4] charSlice2 12XX56789-
    
    [STEP5] Append '+*/' to charSlice2
    
    [STEP5] Result:
    [STEP5] charSlice1 12XX56789-
    [STEP5] charSlice2 12XX56789-+*/
    

    Wile: "That can't be! In Step 5, it didn't change charSlice1! How can it be that the same function, called two times, has two different behaviors?"

    He then decides to add another step (Step 6) where he will change back the X characters of the charSlice2 and produces the following code:

    package main
    
    import "fmt"
    
    func main() {
    	// STEP1: create the first slice with the digit from 1 to 0
    	fmt.Println("[STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)")
    	charSlice1 := []byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
    
    	// STEP2: create a new slice with all the digits from slice 1 but the last one (the 0)
    	fmt.Println("\n[STEP2] Create a slice containing all digits from the first slice but the last one")
    	charSlice2 := charSlice1[:len(charSlice1)-1]
    
    	fmt.Println("\n[STEP2] Result:")
    	fmt.Println("[STEP2]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP2]", "charSlice2", string(charSlice2))
    
    	// STEP3: change the numbers 3 and 4 with an X in charSlice2
    	fmt.Println("\n[STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2")
    	charSlice2[2] = 'X'
    	charSlice2[3] = 'X'
    
    	fmt.Println("\n[STEP3] Result:")
    	fmt.Println("[STEP3]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP3]", "charSlice2", string(charSlice2))
    
    	// STEP4: append a '-' to charSlice2
    	fmt.Println("\n[STEP4] Append '-' to charSlice2")
    	charSlice2 = append(charSlice2, '-')
    
    	fmt.Println("\n[STEP4] Result:")
    	fmt.Println("[STEP4]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP4]", "charSlice2", string(charSlice2))
    
    	// STEP5: append '+*/' to charSlice2
    	fmt.Println("\n[STEP5] Append '+*/' to charSlice2")
    	charSlice2 = append(charSlice2, '+', '*', '/')
    
    	fmt.Println("\n[STEP5] Result:")
    	fmt.Println("[STEP5]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP5]", "charSlice2", string(charSlice2))
    
    	// STEP6: change back the X characters of charSlice2
    	fmt.Println("\n[STEP6] replace the X characters with '2' and '3'")
    	charSlice2[2] = '3'
    	charSlice2[3] = '4'
    
    	fmt.Println("\n[STEP6] Result:")
    	fmt.Println("[STEP6]", "charSlice1", string(charSlice1))
    	fmt.Println("[STEP6]", "charSlice2", string(charSlice2))
    }
    

    But again, the output is not what he was expecting:

    [STEP1] Create a slice containing all the decimal digits (1,2,3,4,5,6,7,8,9,0)
    
    [STEP2] Create a slice containing all digits from the first slice but the last one
    
    [STEP2] Result:
    [STEP2] charSlice1 1234567890
    [STEP2] charSlice2 123456789
    
    [STEP3] Changing the numbers 3 and 4 to the 'X' character in charSlice2
    
    [STEP3] Result:
    [STEP3] charSlice1 12XX567890
    [STEP3] charSlice2 12XX56789
    
    [STEP4] Append '-' to charSlice2
    
    [STEP4] Result:
    [STEP4] charSlice1 12XX56789-
    [STEP4] charSlice2 12XX56789-
    
    [STEP5] Append '+*/' to charSlice2
    
    [STEP5] Result:
    [STEP5] charSlice1 12XX56789-
    [STEP5] charSlice2 12XX56789-+*/
    
    [STEP6] replace the X characters with '2' and '3'
    
    [STEP6] Result:
    [STEP6] charSlice1 12XX56789-
    [STEP6] charSlice2 123456789-+*/
    

    Wile analyzes what happened:

    1. In Step 3, changing charSlice2 changed charSlice1 too, as expected.
    2. In Step 4, appending a character to charSlice2 changed the last character of charSlice1.
    3. in Step 5, appending 3 characters to charSlice2 didn't change charSlice1.
    4. in Step 6, changing charSlice2 didn't change charSlice1.

    Wile: "What is going on? Is this a bug?"

    Explanation

    I can understand why Wile is confused, but the behavior is expected. He's missing some knowledge of how slices work with append function.

    Let's look at the steps of his code one by one.

    Step 1

    charSlice1 := []byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}

    This code is straightforward but does not exactly do what Wile was expecting: charSlice1 is not a 10-byte array.

    Instead, charSlice1 is a view on ten bytes of a ten bytes backing byte array. Its definition is as follows:

    type SliceHeader struct {
        Data uintptr
        Len int
        Cap int
    }

    In that structure:

    • Data is a pointer to the backing array
    • Len is the length of the slice
    • Cap is the size (capacity) of the backing array

    That means that, internally, charSlice1 will have these values:

    • Data: pointer to a 10 bytes array
    • Len: 10
    • Cap: 10

    Graphically, the situation looks as below:

                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |

    Step 2

    charSlice2 := charSlice1[:len(charSlice1)-1]

    Again, this is different from what Wile expects it to do. It isn't copying the array.

    That assignment creates a new slice pointing to the same backing array as charSlice1, but reducing the slice length by 1.

    The values of the SliceHeader for charSlice2 will be:

    • Data: pointer to the same backing array as charSlice1
    • Len: 9
    • Cap: 10

    Since Len is 9, fmt.Println will print only the first nine characters of the array:

                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |
                    |                                   |
                    |-----------------------------------|
                                      |
                                 charSlice2 (len: 9, cap: 10)

    Step 3

    charSlice2[2] = 'X'
    charSlice2[3] = 'X'

    In this code, we are changing the third and the fourth characters of charSlice2. However, since the backing array for charSlice1 and charSlice2 is the same, charSlice1 will be changed, too.

    ​
                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | X | X | 5 | 6 | 7 | 8 | 9 | 0 |
                    |                                   |
                    |-----------------------------------|
                                      |
                                 charSlice2 (len: 9, cap: 10)
    
    ​

    Step 4

    charSlice2 = append(charSlice2, '-')
    

    Here the exciting part starts: appending a character to charSlice2 change the last character of charSlice1. Why?

    Let's see what we have in memory at this point:

                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | X | X | 5 | 6 | 7 | 8 | 9 | 0 |
                    |                                   |
                    |-----------------------------------|
                                      |
                                 charSlice2 (len: 9, cap: 10)

    As you can see, both charSlice1 and charSlice2 refer to the same backing array. The only difference is that charSlice2 has a length of 9.

    When we append a new character to charSlice2, the append function will increase the charSlice2 length and set its last character (the tenth byte of the backing array) with the value to append. Since charSlice1 points to the same backing array as charSlice2, changing the backing array of one slice will also affect the other.

    This is how the situation appears now:

                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | X | X | 5 | 6 | 7 | 8 | 9 | - |
                    |                                       |
                    |---------------------------------------|
                                        |
                                   charSlice2 (len: 10, cap: 10)

    Step 5

    charSlice2 = append(charSlice2, '+', '*', '/')

    In Step 5, we saw that appending three characters to charSlice2 doesn't change charSlice1 anymore. Let's see why.

    In Step 4, we increased the size of charSlice2, so now we have this situation:

                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | X | X | 5 | 6 | 7 | 8 | 9 | - |
                    |                                       |
                    |---------------------------------------|
                                        |
                                   charSlice2 (len: 10, cap: 10)

    What happens is that when we try to append the three characters to charSlice2, we exceed the capacity of the backing array. To accommodate the new data, append creates a bigger backing array, copies the data from charSlice2 to the new array, and returns a fresh slice pointing to the new backing array.

    That means that from now on, charSlice1 is a stale slice and won't be in sync with charSlice2 anymore (since they now refer to 2 different backing arrays).

    The new situation is now like this:

                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | X | X | 5 | 6 | 7 | 8 | 9 | - |
                    |                                       |
                    |---------------------------------------|
    
    
                                   charSlice2 (len: 13, cap: 20)
                                        |
                    |-------------------------------------------------------------------------------|
                    |                                                                               |
    backing_array:  | 1 | 2 | X | X | 5 | 6 | 7 | 8 | 9 | - | + | * | / |   |   |   |   |   |   |   |
                    |                                                                               |
                    |-------------------------------------------------------------------------------|

    Step 6

    charSlice2[2] = '3'
    charSlice2[3] = '4'

    In Step 6, we observe that changing charSlice2 doesn't change charSlice1 anymore.
    After reading the previous paragraph, it should be clear why that happens: charSlice2 now points to a different backing array and has no links anymore with charSlice1.

    This is how the two slices appear after the assignment:

                                   charSlice1 (len: 10, cap: 10)
                                        |
                    |---------------------------------------|
                    |                                       |
    backing_array:  | 1 | 2 | X | X | 5 | 6 | 7 | 8 | 9 | - |
                    |                                       |
                    |---------------------------------------|
    
    
                                   charSlice2 (len: 13, cap: 20)
                                        |
                    |-------------------------------------------------------------------------------|
                    |                                                                               |
    backing_array:  | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | - | + | * | / |   |   |   |   |   |   |   |
                    |                                                                               |
                    |-------------------------------------------------------------------------------|

    Conclusion

    The append function will try to fit the new data into the current backing array. However, if we exceed the current capacity, append will create a new backing array. That means that when working with different slices generated from the same backing array, we have to pay particular attention when we use it since it could modify other slices or diverge totally.

    Related Posts

    • Using Delve to debug Go programs on Red Hat Enterprise Linux

    • Build a Go application using OpenShift Pipelines

    • Go and FIPS 140-2 on Red Hat Enterprise Linux

    • Open vSwitch without stale ports

    Recent Posts

    • Debugging image mode with Red Hat OpenShift 4.20: A practical guide

    • EvalHub: Because "looks good to me" isn't a benchmark

    • SQL Server HA on RHEL: Meet Pacemaker HA Agent v2 (tech preview)

    • Deploy with confidence: Continuous integration and continuous delivery for agentic AI

    • Every layer counts: Defense in depth for AI agents with Red Hat AI

    What’s up next?

    Convert2RHEL is a command-line utility that can streamline your migration path from CentOS Linux 7 to a fully supported Red Hat Enterprise Linux (RHEL) operating system. This cheat sheet outlines how to convert your CentOS Linux instance to RHEL in just 7 steps.

    Get the cheat sheet
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Chat Support

    Please log in with your Red Hat account to access chat support.