Every language
15 langages, copy-ready. One at a time with syntax highlighting, or all inline.
SQLSQLrunnable
-- PostgreSQL: join an ARRAY with a delimiter
SELECT array_to_string(ARRAY['go', 'rust', 'zig'], ', ') AS langs;
-- langs: go, rust, zig
-- Joining ROWS is a different job — aggregate, don't array-join:
SELECT string_agg(name, ', ' ORDER BY name) AS langs
FROM (VALUES ('zig'), ('go'), ('rust')) AS t(name);
-- langs: go, rust, zigarray_to_string joins an array; string_agg (STRING_AGG in SQL Server, GROUP_CONCAT in MySQL) concatenates rows — mixing them up is the perennial SQL mistake. NULLs vanish in both by default; MySQL's GROUP_CONCAT needs SEPARATOR ', ' and truncates at group_concat_max_len (default 1024).
JSJavaScript
const langs = ['go', 'rust', 'zig'];
console.log(langs.join(', ')); // go, rust, zig
// join() COERCES instead of throwing — know the rules:
console.log([1, null, undefined, [2, 3]].join('|')); // 1|||2,3
// null/undefined become '' and nested arrays flatten ONE level,
// so the separator inside the nested array is ',' — not yours.[] .join() returns '' (never throws), and a Symbol element is the only thing that raises TypeError. If nulls should disappear rather than render as empty fields, filter them before joining.
TSTypeScript
const parts: string[] = ['a', 'b', 'c'];
const csv: string = parts.join(',');
// Typed arrays of string | null join fine at runtime but lie about
// content — scrub before joining to keep the type honest:
const dirty: (string | null)[] = ['a', null, 'c'];
const clean: string = dirty.filter((p): p is string => p !== null).join(',');
console.log(csv, clean); // a,b,c a,cjoin() accepts readonly arrays too (readonly string[]), so functions taking lists they will not mutate can join without copying.
GoGo
package main
import (
"fmt"
"strings"
)
func main() {
langs := []string{"go", "rust", "zig"}
fmt.Println(strings.Join(langs, ", ")) // go, rust, zig
}strings.Join takes []string only — convert other element types first with a map. It never appends the trailing separator and []string{} yields "". For numbers, fmt.Sprint(a, b, ...) inserts spaces between operands, so it is not a join.
RsRust
fn main() {
let langs = vec!["go", "rust", "zig"];
println!("{}", langs.join(", ")); // go, rust, zig
// Non-strings: map to String first, then join.
let nums: Vec<String> = [1, 2, 3].iter().map(|n| n.to_string()).collect();
println!("{}", nums.join("-")); // 1-2-3
}join comes from the Join slice trait — it works on Vec<&str>, Vec<String> and iterators collected into either, and borrows nothing you do not hand it. The separator is a str, and an empty slice yields an empty String.
PHPPHP
<?php
$langs = ['go', 'rust', 'zig'];
echo implode(', ', $langs), PHP_EOL; // go, rust, zig
// null silently becomes ''; scalars are cast; nested arrays WARN:
$dirty = ['a', null, 3];
echo implode('|', $dirty), PHP_EOL; // a||3implode (alias join) is argument-order agnostic since PHP 8 — implode($sep, $arr) and implode($arr, $sep) both work. A nested array raises 'Array to string conversion', so map first with array_map('strval', $arr) for mixed data.
PyPython
langs = ["go", "rust", "zig"]
print(", ".join(langs)) # go, rust, zig
print("-".join(str(n) for n in [1, None, 3])) # 1-None-3 — None is NOT skipped
# Non-str elements raise TypeError, and the message is the teacher:
try:
", ".join(["a", 1])
except TypeError as err:
print(err) # sequence item 1: expected str instance, int foundjoin is a STRING method, not a list method — '" ".join(items)', never 'items.join(...)'. Contrast with JavaScript: Python stringifies None as 'None' rather than dropping it to ''.
CC
#include <stdio.h>
#include <string.h>
/* Join n parts with sep into out (size bytes). NUL-terminates, truncates
safely on overflow, and never emits the separator after the last part.
Returns out. */
char *join(char *out, size_t size, const char *sep, const char **parts, size_t n) {
size_t pos = 0;
out[0] = '\0';
for (size_t i = 0; i < n; i++) {
size_t len = strlen(parts[i]);
size_t sep_len = i > 0 ? strlen(sep) : 0;
if (pos + sep_len + len + 1 > size) {
break; /* not enough room for part + NUL — stop here */
}
if (i > 0) {
memcpy(out + pos, sep, sep_len);
pos += sep_len;
}
memcpy(out + pos, parts[i], len);
pos += len;
out[pos] = '\0';
}
return out;
}
int main(void) {
const char *parts[] = {"go", "rust", "zig"};
char buf[64];
printf("%s\n", join(buf, sizeof buf, ", ", parts, 3)); /* go, rust, zig */
return 0;
}No stdlib join exists — the bugs are always the same two: a separator after the last element and a buffer overflow. The explicit size argument and the room-for-NUL check are what make this safe; snprintf into a cursor is the other accepted idiom.
C++C++
#include <iostream>
#include <string>
#include <vector>
// Separator BETWEEN elements only — the `if (i > 0)` guard is what
// kills the trailing-separator bug. C++23 adds std::views::join_with.
std::string join(const std::vector<std::string>& parts, const std::string& sep) {
std::string out;
for (std::size_t i = 0; i < parts.size(); ++i) {
if (i > 0) out += sep;
out += parts[i];
}
return out;
}
int main() {
std::cout << join({"go", "rust", "zig"}, ", ") << '\n'; // go, rust, zig
}reserve() the output when sizes are known, or fold with std::accumulate for one-liners. fmt::join (or std::format's ranged join in C++23) formats non-strings without pre-converting.
C#C#
string[] langs = { "go", "rust", "zig" };
Console.WriteLine(string.Join(", ", langs)); // go, rust, zig
// Accepts IEnumerable<T> and calls ToString; null renders as empty:
Console.WriteLine(string.Join(", ", new object?[] { 1, null, "x" })); // 1, , xstring.Join overloads take params arrays, IEnumerable<string>, and IEnumerable<T> — so LINQ sequences join without materializing. For comma-critical output (CSV), quote fields separately; Join does no escaping.
JvJava
import java.util.List;
public class JoinDemo {
public static void main(String[] args) {
List<String> langs = List.of("go", "rust", "zig");
System.out.println(String.join(", ", langs)); // go, rust, zig
}
}String.join accepts CharSequence only (String, StringBuilder, ...) — map other types first. Null elements are NOT dropped: String.join(",", "a", null) prints 'a,null'. For streams use Collectors.joining(", ").
SwSwift
let langs = ["go", "rust", "zig"]
print(langs.joined(separator: ", ")) // go, rust, zig
// Non-strings: map to String first.
print([1, 2, 3].map(String.init).joined(separator: "-")) // 1-2-3joined() on [String] yields String, but joined(separator:) on a slice of Substring views is also fine. The no-separator joined() concatenates directly. Empty arrays yield "" — never an optional.
KtKotlin
fun main() {
val langs = listOf("go", "rust", "zig")
println(langs.joinToString(", ")) // go, rust, zig
println(listOf(1, null, 3).joinToString("-")) // 1-null-3
}joinToString carries the kitchen sink: prefix = "[", postfix = "]", limit = 3, truncated = "…" — logging-friendly out of the box. Like Java, null elements render as the literal 'null'.
RbRuby
langs = %w[go rust zig]
puts langs.join(', ') # go, rust, zig
# nil stringifies to '' — and nested arrays are flattened RECURSIVELY,
# with your separator applied inside them too:
puts [1, nil, [2, 3]].join('|').inspect # "1||2|3"Array#join flattens nested arrays to arbitrary depth, which quietly changes your delimiter structure. %w[...] literals are the idiomatic source of string arrays; [].join(',') is "".
ZigZig
const std = @import("std");
pub fn main() !void {
const parts = [_][]const u8{ "go", "rust", "zig" };
const sep = ", ";
// Size the buffer exactly (saturating sub guards the empty case),
// allocate once, then fill — no trailing separator.
var total: usize = 0;
for (parts) |p| total += p.len;
total += sep.len * (parts.len -| 1);
const a = std.heap.page_allocator;
const out = try a.alloc(u8, total);
defer a.free(out);
var pos: usize = 0;
for (parts, 0..) |p, i| {
if (i > 0) {
@memcpy(out[pos..][0..sep.len], sep);
pos += sep.len;
}
@memcpy(out[pos..][0..p.len], p);
pos += p.len;
}
std.debug.print("{s}\n", .{out}); // go, rust, zig
}No stdlib join — allocate total + separators in one shot, then memcpy each part. `parts.len -| 1` (saturating) keeps the empty-array case from underflowing, and Zig 0.11+ multi-object for loops give you the index for the separator test.