Filter A Slice Into A New One
Fast recall
Quick revision
Core answer
Practical understanding of Slice Tricks.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
The straightforward form allocates a fresh destination:
filtered := make([]int, 0)
for _, v := range all {
if v%2 == 0 {
filtered = append(filtered, v)
}
}New storage, source untouched — the safe default.
The in-place form starts the output AT the source's first slot and overwrites forward:
evens := all[:0]
for _, v := range all {
if v%2 == 0 {
evens = append(evens, v)
}
}
// all is now CLOBBERED — verified: [2 4 3 4 5] after filtering [1 2 3 4 5]Zero allocations — the kept values are written into the same backing array the loop is reading from, always at or behind the read position, so no unread value is lost. The price is explicit: the source slice's contents are destroyed (its length may still say 5, but the tail is garbage). This is the standard idiom inside libraries where the input is a scratch copy; it's a bug in application code that keeps using the input afterward.
The interview-useful framing: know both, and treat the in-place filter as an optimization with a visible precondition — 'the caller no longer needs the input' — that should be stated at the function boundary, not discovered by a reader at 2 a.m.
Filter A Slice Into A New One
Direct Answer
Append matches onto a fresh slice — filtered := make([]T, 0); for ... append. Or the in-place filter: out := s[:0] — reuses storage but overwrites the source.
Deep Dive & Architecture
The in-place filter is the idiom that looks like a bug and isn't — until it is. Its safety rests on one invariant: the write index never passes the read index.
evens := all[:0]
for _, v := range all {
if v%2 == 0 {
evens = append(evens, v)
}
}
The output starts at all's element 0. The input is read by range at index r (0, 1, 2, ...). Each append writes at index w — and w can only advance when r advanced past it, because w increments only for kept elements and r increments for every element. So w <= r always: the write to slot w can never clobber a value the loop hasn't read. Executed: [1 2 3 4 5] → evens [2 4], and all reads [2 4 3 4 5] — the first two slots overwritten, the tail garbage-but-unread.
That's the safety. The cost is the aliasing: all and evens share storage, so 'all' after the loop is corrupted from its original meaning. Every future reader of all is reading a lie unless the function's contract says the input is consumed.
Which is exactly why the fresh-destination version is the default in application code — the same three lines with make(...) instead of s[:0], zero cleverness, no preconditions. The in-place form earns its place inside library code where the input is already a private copy. The interview-grade answer names the invariant (w <= r), the corruption (all is consumed), and the ownership rule: use in-place only when you already own a disposable copy.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is Slice Tricks, and when would you use it?
- When would you use Slice Tricks in a real project?
- How would you compare Slice Tricks with an alternative?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.