惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
Security Latest
Security Latest
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
A
Arctic Wolf
T
Tor Project blog
T
Threatpost
NISL@THU
NISL@THU
I
InfoQ
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Apple Machine Learning Research
Apple Machine Learning Research
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
K
Kaspersky official blog
W
WeLiveSecurity
L
LINUX DO - 热门话题
小众软件
小众软件
Recorded Future
Recorded Future
B
Blog RSS Feed
H
Help Net Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
L
Lohrmann on Cybersecurity
Last Week in AI
Last Week in AI
Stack Overflow Blog
Stack Overflow Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
Blog — PlanetScale
Blog — PlanetScale
雷峰网
雷峰网
爱范儿
爱范儿
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Hacker News: Front Page
博客园 - 三生石上(FineUI控件)
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
N
News and Events Feed by Topic
云风的 BLOG
云风的 BLOG
Scott Helme
Scott Helme
V2EX - 技术
V2EX - 技术

VictoriaMetrics: Simple & Reliable Monitoring for Everyone on VictoriaMetrics

Operator now has Long-Term Support (LTS) version Multi-tiered Observability: A Practical Way to Handle Diverse Workloads VictoriaMetrics April 2026 Ecosystem Updates Not All Telemetry Requires Premium Pricing VictoriaMetrics at KubeCon Amsterdam: Community Highlights What's new in VictoriaMetrics Anomaly Detection (Q1 2026) What's New in VictoriaMetrics Cloud Q1 2026? Logs, MCP Server, Better Alerting, and... a Secret Project VictoriaMetrics at KubeCon: Optimizing Tail Sampling in OpenTelemetry with Retroactive Sampling VictoriaMetrics March 2026 Ecosystem Updates Observability Lessons From OpenAI Benchmarking Kubernetes Log Collectors: vlagent, Vector, Fluent Bit, OpenTelemetry Collector, and more VictoriaMetrics February 2026 Ecosystem Updates VictoriaMetrics at FOSDEM, Cloud Native Days France, and CfgMgmtCamp Ghent VictoriaLogs in VictoriaMetrics Cloud: Fast, Cost-Effective Log Management is Here What’s new in VictoriaMetrics Anomaly Detection (2025) VictoriaMetrics January 2026 Ecosystem Updates VictoriaLogs Basics: What You Need to Know, with Examples & Visuals What's New in VictoriaMetrics Cloud Q4 2025? New tiers, more deployment options, IaC and alerting rules. Vibe coding tools observability with VictoriaMetrics Stack and OpenTelemetry How a US Software Provider Improved Traffic Alerting with VictoriaMetrics Anomaly Detection VictoriaMetrics 2025 Developer Experience: A Year in Review Spotify’s performance & control across large monitoring environments with VictoriaMetrics VictoriaMetrics Achieves Red Hat OpenShift Operator Certification Our latest updates across the VictoriaMetrics Observability ecosystem New Capacity Tiers in VictoriaMetrics Cloud Announcing 1B+ Downloads & Product Development With Logs, Traces, Metrics AI Agents Observability with OpenTelemetry and the VictoriaMetrics Stack Discarding gRPC-Go: The Story Behind OTLP/gRPC Support in VictoriaTraces What's New in VictoriaMetrics Cloud Q3 2025? From new region in Asia to proactive alerts How DreamHost Slashed Memory Usage by 80% and Scaled to 76 Million Time Series Upcoming Conferences & Meetups: Where to Meet Our Team VictoriaMetrics Long-Term Support (LTS): H2 2025 Update Creating a Sustainable Open Source Business Model - Introduction Full-Stack Observability with VictoriaMetrics in the OTel Demo Alerting Best Practices vmanomaly Deep Dive: Smarter Alerting with AI (Tech Talk Companion) VictoriaLogs Practical Ingestion Guide for Message, Time and Streams Monotonic and Wall Clock Time in the Go time package Hello Singapore! VictoriaMetrics Cloud Expands to Asia Pacific MCP Server Integration & Much More: What's New in VictoriaMetrics Cloud Q2 2025 FIPS 140-3 Compatible Builds for VictoriaMetrics Enterprise Components VictoriaLogs Unleashed: Cluster Version Now Available for Exceptional, Linear Scaling Integrations made easy with VictoriaMetrics Cloud Developer's Note: Research on Distributed Tracing, Comparing With Tempo and ClickHouse vmagent: Key Features Explained in Under 15 Minutes Go synctest: Solving Flaky Tests vmalert: Maximize Your Monitoring (Tech Talk Companion) Celebrating 14K Stars on GitHub: Spring Update vmalert: Maximize Your Monitoring VictoriaMetrics Connects with the Open Source Community at LinuxFest Northwest 2025 Graceful Shutdown in Go: Practical Patterns VictoriaLogs: Gaps, Gains & Growth Prometheus Monitoring: Functions, Subqueries, Operators, and Modifiers VictoriaMetrics Cloud: What's New in Q1 2025? Don’t default to microservices: You’ll thank us later! Container CPU Requests & Limits Explained with GOMAXPROCS Tuning gRPC in Go: Streaming RPCs, Interceptors, and Metadata From Chaos to Clarity with VictoriaLogs Prometheus Alerting 101: Rules, Recording Rules, and Alertmanager Heading to London: Meet Our Team at KubeCon Europe 2025 Inside vmselect: The Query Processing Engine of VictoriaMetrics Meet Our Team at Scale 22x Practical Protobuf - From Basic to Best Practices VictoriaLogs Status Update: Heading Towards the Cluster Version 24th of February 2025 Statement: VictoriaMetrics Stands with Ukraine! Prometheus Metrics Explained: Counters, Gauges, Histograms & Summaries Prometheus Monitoring: Instant Queries and Range Queries Explained 300%+ Growth in 2024: Join Our Team in 2025! FOSDEM 2025 recap How Protobuf Works—The Art of Data Encoding OpenTelemetry, Prometheus, and More: Which Is Better for Metrics Collection and Propagation? How vmstorage Handles Query Requests From vmselect How vmstorage's IndexDB Works VictoriaMetrics Tech Talk Stream: A Deep Dive into Blackbox Monitoring How HTTP/2 Works and How to Enable It in Go VictoriaMetrics Cloud: What's New in Q4 2024? How vmstorage Processes Data: Retention, Merging, Deduplication,... How vmstorage Handles Data Ingestion From vminsert When Metrics Meet vminsert: A Data-Delivery Story From net/rpc to gRPC in Go Applications Piros | VictoriaMetrics Partner Allenta | VictoriaMetrics Partner CloudRaft | VictoriaMetrics Partner Sensedia & VictoriaMetrics: API-compatible Efficient Storage Scalable Prometheus: Why DSV Chose VictoriaMetrics Sensor Factory | VictoriaMetrics Partner Erythix | VictoriaMetrics Partner Groove X & VictoriaMetrics: Faster Device Health Monitoring Scaled & Performant Monitoring at Spotify with VictoriaMetrics Grammarly & VictoriaMetrics: 10× Lower Costs & Direct Access Zelarsoft | VictoriaMetrics Partner DFKI & VictoriaMetrics: Efficient Long-Term Metric Storage Niubits | VictoriaMetrics Partner Megazone Cloud | VictoriaMetrics Partner Cogito Software | VictoriaMetrics Partner Bajau | VictoriaMetrics Partner Find Out Why Dig Security Chose VictoriaMetrics! Ness | VictoriaMetrics Partner Alpha Data | VictoriaMetrics Partner SIOS Technology | VictoriaMetrics Partner
How Go Arrays Work and Get Tricky with For-Range
Phuong Le · 2024-08-02 · via VictoriaMetrics: Simple & Reliable Monitoring for Everyone on VictoriaMetrics

The classic Golang array and slice are pretty straightforward. Arrays are fixed-size, and slices are dynamic. But I’ve got to tell you, Go might seem simple on the surface, but it’s got a lot going on under the hood.

As always, we’ll start with the basics and then dig a bit deeper. Don’t worry, arrays get pretty interesting when you look at them from different angles.

We’ll cover slices in the next part, I’ll drop that here once it’s ready., it’s already published: Slices in Go: Grow Big or Go Home.

What is an array?

#

Arrays in Go are a lot like those in other programming languages. They’ve got a fixed size and store elements of the same type in contiguous memory locations.

This means Go can access each element quickly since their addresses are calculated based on the starting address of the array and the element’s index.

func main() {
	arr := [5]byte{0, 1, 2, 3, 4}
	println("arr", &arr)

	for i := range arr {
		println(i, &arr[i])
	}
}

// arr 0x1400005072b
// 0 0x1400005072b
// 1 0x1400005072c
// 2 0x1400005072d
// 3 0x1400005072e
// 4 0x1400005072f

There are a couple of things to notice here:

  • The address of the array arr is the same as the address of the first element.
  • The address of each element is 1 byte apart from each other because our element type is byte.

Array [5]byte{0, 1, 2, 3, 4} in memory

Array [5]byte{0, 1, 2, 3, 4} in memory

Look at the image carefully.

Our stack is growing downwards from a higher to a lower address, right? This picture shows exactly how an array looks in the stack, from arr[4] to arr[0].

So, does that mean we can access any element of an array by knowing the address of the first element (or the array) and the size of the element? Let’s try this with an int array and unsafe package:

func main() {
	a := [3]int{99, 100, 101}

	p := unsafe.Pointer(&a[0])

	a1 := unsafe.Pointer(uintptr(p) + 8)
	a2 := unsafe.Pointer(uintptr(p) + 16)

	fmt.Println(*(*int)(p))
	fmt.Println(*(*int)(a1))
	fmt.Println(*(*int)(a2))
}

// Output:
// 99
// 100
// 101

Well, we get the pointer to the first element and then calculate the pointers to the next elements by adding multiples of the size of an int, which is 8 bytes on a 64-bit architecture. Then we use these pointers to access and convert them back to the int values.

Array [3]int{99, 100, 101} in memory

Array [3]int{99, 100, 101} in memory

The example is just a play around with the unsafe package to access memory directly for educational purposes. Don’t do this in production without understanding the consequences.

Now, an array of type T is not a type by itself, but an array with a specific size and type T, is considered a type. Here’s what I mean:

func main() {
    a := [5]byte{}
    b := [4]byte{}

    fmt.Printf("%T\n", a) // [5]uint8
    fmt.Printf("%T\n", b) // [4]uint8

    // cannot use b (variable of type [4]byte) as [5]byte value in assignment
    a = b 
}

Even though both a and b are arrays of bytes, the Go compiler sees them as completely different types, the %T format makes this point clear.

Here is how the Go compiler sees it internally (src/cmd/compile/internal/types2/array.go):

// An Array represents an array type.
type Array struct {
	len  int64
	elem Type
}

// NewArray returns a new array type for the given element type and length.
// A negative length indicates an unknown length.
func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} }

The length of the array is “encoded” in the type itself, so the compiler knows the length of the array from its type. Trying to assign an array of one size to another, or compare them, will result in a mismatched type error.

Array literals

#

There are many ways to initialize an array in Go, and some of them might be rarely used in real projects:

var arr1 [10]int // [0 0 0 0 0 0 0 0 0 0]

// With value, infer-length
arr2 := [...]int{1, 2, 3, 4, 5} // [1 2 3 4 5]

// With index, infer-length
arr3 := [...]int{11: 3} // [0 0 0 0 0 0 0 0 0 0 0 3]

// Combined index and value
arr4 := [5]int{1, 4: 5} // [1 0 0 0 5]
arr5 := [5]int{2: 3, 4, 4: 5} // [0 0 3 4 5]

What we’re doing above (except for the first one) is both defining and initializing their values, which is called a “composite literal.” This term is also used for slices, maps, and structs.

Now, here’s an interesting thing: when we create an array with less than 4 elements, Go generates instructions to put the values into the array one by one.

So when we do arr := [4]int{1, 2, 3, 4}, what’s actually happening is:

arr := [4]int{}
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4

This strategy is called local-code initialization. This means that the initialization code is generated and executed within the scope of a specific function, rather than being part of the global or static initialization code.

It’ll become clearer when you read another initialization strategy below, where the values aren’t placed into the array one by one like that.

“What about arrays with more than 4 elements?”

The compiler creates a static representation of the array in the binary, which is known as ‘static initialization’ strategy.

This means the values of the array elements are stored in a read-only section of the binary. This static data is created at compile time, so the values are directly embedded into the binary. If you’re curious how [5]int{1,2,3,4,5} looks like in Go assembly:

main..stmp_1 SRODATA static size=40
	0x0000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00  ................
	0x0010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00  ................
	0x0020 05 00 00 00 00 00 00 00                          ........

It’s not easy to see the value of the array, we can still get some key info from this.

Our data is stored in stmp_1, which is read-only static data with a size of 40 bytes (8 bytes for each element), and the address of this data is hardcoded in the binary.

The compiler generates code to reference this static data. When our application runs, it can directly use this pre-initialized data without needing additional code to set up the array.

const readonly = [5]int{1, 2, 3, 4, 5}

arr := readonly

“What about an array with 5 elements but only 3 of them initialized?”

Good question, this literal [5]int{1,2,3} falls into the first category, where Go puts the value into the array one by one.

While talking about defining and initializing arrays, we should mention that not every array is allocated on the stack. If it’s too big, it gets moved to the heap.

But how big is “too big,” you might ask.

As of Go 1.23, if the size of the variable, not just array, exceeds a constant value MaxStackVarSize, which is currently 10 MB, it will be considered too large for stack allocation and will escape to the heap.

func main() {
	a := [10 * 1024 * 1024]byte{}
	println(&a)

	b := [10*1024*1024 + 1]byte{}
	println(&b)
}

In this scenario, b will move to the heap while a won’t.

Array operations

#

The length of the array is encoded in the type itself. Even though arrays don’t have a cap property, we can still get it:

func main() {
    a := [5]int{1, 2, 3}
    println(len(a)) // 5
    println(cap(a)) // 5
}

The capacity equals the length, no doubt, but the most important thing is that we know this at compile time, right?

So len(a) doesn’t make sense to the compiler because it’s not a runtime property, Go compiler knows the value at compile time. Go takes this key point and turns it into a constant behind the scenes. What the Go compiler sees is:

func main() {
    a := [5]int{1, 2, 3}
    println(5) 
    println(5)
}

Slicing is a way to get a slice from an array, and its full form is denoted by the syntax [start:end:capacity]. Usually, you’ll see its variants: [start:end], [:end], [start:], [:].

start is the index of the first element to include in the new slice (inclusive), end is the index of the last element to exclude from the new slice (exclusive), and capacity is an optional argument that specifies the capacity of the new slice.

Let’s ignore capacity for now; it will be fully explained in the slice post.

func main() {
    a := [5]int{0, 1, 2, 3, 4}

    // new slice from a[1] to a[3-1]
    b := a[1:3]  // [1 2]

    // new slice from a[0] to a[3-1]
    c := a[:3] // [0 1 2]

    // new slice from a[1] to a[5-1]
    d := a[1:] // [1 2 3 4]
}

The compiler evaluates the slicing indices (start, end, and capacity) to determine the bounds of the new slice.

Array slicing example

Array slicing example

If any of the indices are missing, they default to:

  • start defaults to 0.
  • end defaults to the length of the original slice or array,
  • capacity defaults to the capacity of the original slice or the length of the original array.

“What about the new length and capacity of the slice?”

The new length is determined by subtracting the start index from the end index, and the new capacity is determined by subtracting the start index from either the capacity argument (if provided) or the original capacity.

When we write b := a[1:3], here’s what’s really going on:

b.array = &a[1]
b.len = 3-1
b.cap = 5-1

Regarding the panic when we specify the end index out of bounds: because the end is exclusive, we can specify it to be equal to the length of the original array:

func main() {
    a := [5]int{1, 2, 3, 4, 5}
    b := a[4:5] // [4] 
}

But, can we specify start at 5, like a[5:]? Take a guess before reading on.

func main() {
    a := [5]int{1, 2, 3, 4, 5}
    b := a[5:] // []
}

This might be surprising, the answer is yes.

What we are creating is an empty slice, with no length and no capacity. So, the general rule for bound-checking the slicing is: 0 <= start <= end <= cap <= real capacity.

“But the array underlying b is pointing to a[5], which is out of bounds, right?”

No, Go has special rules to handle this case. The underlying array will still point to a[0], but this slice b is useless.

Array are values, raw values.

#

In some other languages, an array variable is basically a pointer to the first element of the array. When you pass an array to a function, what’s actually passed is a pointer, not the whole array. So, changing the array elements within the function will affect the original array.

In contrast, Go treats arrays as value types. This means an array variable in Go represents the entire array, not just a reference to its first element, even though printing &a gives the same address as &a[0].

When you pass an array to a function in Go, the entire array is copied:

func doSomething(a [5]byte) {
    a[0] = 1
}

func main() {
    a := [5]byte{}
    doSomething(a)
    fmt.Println(a)
}

// [0 0 0 0 0]

The output is expected since we’re modifying the copied array, not the original one.

“Isn’t it inefficient? It’s always copied.”

It’s only inefficient if you pass a large array to a function and benchmarks or profilers show it’s a bottleneck. Otherwise, we can just pass arrays as usual.

Here’s a tricky thing when you loop over your array, especially using a for-range loop. Let’s start with a quick example:

func main() {
	a := [3]int{1, 2, 3}
	b := [3]int{4, 5, 6}

	for i, v := range a {
		if i == 1 {
			a = b
		}
		fmt.Println(v)
	}
}

In the snippet, a and b are [3]int so we can assign them, but we assign a = b right in the loop.

So, what’s your guess? I have three options: 1 2 3, 1 5 6, or 1 2 6.

When we iterate to index 1, we immediately change the array, so the output should be 1 2 6, because v already evaluated to 2 before we assign. Unexpectedly, the output is 1 2 3, just like nothing happened. So, did a change, or did our assignment have no effect?

“Oh, the arr used inside the loop is a copy of the original arr, right?”

Good thinking, but that’s only half the truth. Let me explain the rest.

Go indeed makes a copy, but the copy is hidden from us, and only v can see that copied array. The array a we use in the loop is still our original a, and if you print it out after the loop, it’ll be [4 5 6], we could think of another scenario like the one shown below:

Array with for-range

Array with for-range

Here’s what Go sees:

func main() {
	a := [3]int{1, 2, 3}
	b := [3]int{4, 5, 6}

    a1 := a
	for i, v := range a1 {
		if i == 1 {
			a = b
		}
		fmt.Println(v)
	}
}

Got the idea yet? Only v sees a1, and this is hidden from our perspective as users. This happens even if you don’t assign or change a in the loop.

That means it works like a pass-by-value case. If our array is much bigger than just several elements, making a copy like this will be inefficient and the Go team has optimized this for us by allowing for-range with a pointer to the array.

func main() {
	a := [3]int{1, 2, 3}
	b := [3]int{4, 5, 6}

	for i, v := range &a {
		if i == 1 {
			a = b
		}
		fmt.Println(v)
	}
}

The output is now 1 2 6, just like we initially expected.

But the key takeaway here isn’t to encourage changing the array inside the loop, I’d not recommend that. Instead, it’s to show that Go supports for-range with a pointer to an array, while it doesn’t support pointers to slices. And now you know why.

Stay Connected

#

Hi, I’m Phuong Le, a software engineer at VictoriaMetrics. The writing style above focuses on clarity and simplicity, explaining concepts in a way that’s easy to understand, even if it’s not always perfectly aligned with academic precision.

If you spot anything that’s outdated or if you have questions, don’t hesitate to reach out. You can drop me a DM on X(@func25).

Related articles:

Who We Are

#

If you want to monitor your services, track metrics, and see how everything performs, you might want to check out VictoriaMetrics. It’s a fast, open-source, and cost-saving way to keep an eye on your infrastructure.

And we’re Gophers, enthusiasts who love researching, experimenting, and sharing knowledge about Go and its ecosystem.