Copy or share?
Think of a document you want a colleague to review.
- You can print a copy and hand it over. They can scribble all over it; your original is untouched. That’s a value type.
- You can share a link to the online document. There’s still one document, now with two people who can edit it. Whatever they change, you see. That’s a reference type.
In code, a variable either holds the data itself (the printed copy) or a reference to data that lives somewhere else (the link). Assigning or passing it to a function copies whatever the variable holds: the data, or the link.
A value typeValue typeA type whose values are copied on assignment, so each variable has its own independent data.Open in glossary → has copy semantics: assignment, parameter passing and capture produce an independent value, and identity is meaningless (two equal values are interchangeable). A reference typeReference typeA type whose variables hold a reference to a shared object, so assignment shares the object instead of copying it.Open in glossary → has identity: variables hold references, and mutation through one is visible through all.
Swift makes value types first-class (struct, enum, tuples, and the standard collections, which use copy-on-writeCopy-on-writeSharing data between copies until one of them is changed, and only then making a real copy.Open in glossary →). On the JVM, the only value types are the eight primitives: Kotlin data class and Java record are reference types with value-based equals, which is not the same thing. The Detailed version covers where that difference bites: shallow copies, accidental quadratic copying, and value types across threads.
struct UserValue { var name: String }
final class UserObject { var name: String; init(name: String) { self.name = name } }
var a = UserValue(name: "Ana")
var b = a // copies the data
b.name = "Marko"
print(a.name) // Ana
let c = UserObject(name: "Ana")
let d = c // copies the reference
d.name = "Marko"
print(c.name) // Markodata class User(var name: String)
val a = User("Ana")
val b = a // copies the reference: data classes are still objects
b.name = "Marko"
println(a.name) // Marko
val c = User("Ana")
val d = c.copy() // an explicit copy
d.name = "Marko"
println(c.name) // Anaclass User {
String name;
User(String name) { this.name = name; }
}
User a = new User("Ana");
User b = a; // copies the reference
b.name = "Marko";
System.out.println(a.name); // Marko
int x = 1;
int y = x; // primitives are values
y = 2;
System.out.println(x); // 1interface User { name: string }
const a: User = { name: "Ana" };
const b = a; // copies the reference
b.name = "Marko";
console.log(a.name); // Marko
const c: User = { name: "Ana" };
const d = { ...c }; // an explicit (shallow) copy
d.name = "Marko";
console.log(c.name); // Ana
let x = 1;
let y = x; // numbers, strings and booleans are values
y = 2;
console.log(x); // 1The rule of thumb for each language:
- Swift:
structandenumare values,classis a reference. Most Swift types you use every day are structs:String,Int,Array,Dictionary, and SwiftUI views. - Kotlin: everything is a reference, except numbers,
CharandBoolean, which behave like values. Adata classis still a reference; it just gives youcopy()to make a new one. - Java: the eight primitives (
int,double,boolean…) are values. Everything else, includingString, arrays and records, is a reference. - TypeScript: primitives (
number,string,boolean,bigint…) are values. Objects, arrays,Maps and class instances are references;{ ...obj }and[...arr]make a copy.
Swift note
In Swift, the choice is yours: model data as a struct by default, and reach for a class when you need shared identity (one object that several parts of the app observe or change), inheritance, or Objective-C interop.
Java note
Java’s String is a reference type, but it’s immutable: no method changes it, so sharing it is safe. Immutability is how JVM code gets value-like behavior from reference types.
TypeScript note
TypeScript has no way to declare your own value types: every object is shared by reference. Value-like behavior comes from treating objects as immutable (readonly properties) and creating a new object for every change.
Deep dives
Swift
Deep diveCopy-on-write: why big Swift values stay cheapInterview favorite
Array, Dictionary, Set and String are structs, but their elements live in a heap buffer. Copying the struct copies only a reference to that buffer, so let b = a is O(1). The real copy happens on the first mutation of a shared buffer: the mutating method checks isKnownUniquelyReferenced and duplicates the storage only if someone else still points to it.
That’s why passing arrays around is cheap, and why a mutation inside a loop can suddenly cost O(n) if another variable still holds the old array. Your own structs that wrap a large class get the same behavior only if you implement the uniqueness check yourself.
Kotlin
Deep diveValue classes: a wrapper without the allocationInterview favorite
@JvmInline value class UserId(val value: String) gives you a distinct type (you can’t pass a UserId where an OrderId is expected) that compiles down to the wrapped String in most places: no extra object, no extra allocation.
It’s boxed when used where the JVM needs an object: as a generic type argument, as a nullable type, or through an interface. Equality is by value, and === isn’t allowed on value classes. Separately, Project Valhalla is working on true value types for the JVM itself, which would remove much of that boxing.
Java
Deep diveWhy 127 == 127 but 128 != 128Interview favorite
Autoboxing uses Integer.valueOf, which returns cached objects for values from -128 to 127. So Integer a = 127, b = 127; gives two references to the same object and a == b is true, while with 128 each valueOf creates a new object and == is false.
Interviewers love it because code that compares boxed numbers with == passes every test with small ids and fails in production. Compare boxed values with equals (or Objects.equals), and keep == for primitives and identity checks.
TypeScript
Deep divestructuredClone vs JSON round-tripsInterview favorite
JSON.parse(JSON.stringify(x)) was the classic deep copy, and it’s lossy: Dates become strings, undefined properties and functions disappear, Map and Set become {}, and cycles throw.
structuredClone(x) (in browsers and Node 17+) copies Date, Map, Set, typed arrays and cyclic references correctly. It still can’t copy functions, and class instances come back as plain objects without their prototype, so methods are gone. For immutable updates of plain data, spread copies ({ ...a, b: { ...a.b } }) are usually enough and faster.
| Swift | Kotlin | Java | TypeScript | |
|---|---|---|---|---|
| Value types | struct, enum, tuples, String, collections (copy-on-write) | primitives only (Int, Double, Boolean, Char…) | the 8 primitives | primitives only (number, string, boolean, bigint…) |
| Value-like by immutability | let struct | data class with val + read-only collections, @JvmInline value class | record + List.copyOf | readonly / as const objects, replaced on change |
Swift
Value semantics are about copying and identity, not memory layout: structs can live on the heap (in an existential, captured by an escaping closure) and still behave as values. Choose a class when you need shared identity, inheritance or Objective-C interop.
Kotlin, Java
On the JVM the only value types are the primitives: data class and record are reference types with value-based equals, which is not the same thing. Value-like behavior comes from immutability: nobody can change a shared object.
TypeScript
Only primitives are values. Objects, arrays, Maps and class instances are always shared by reference, so value-like behavior comes from immutability by convention: readonly types and creating a new object on every change.
Check this part
var b = a, whereais a struct. You changeb.name. What isa.name?val b = a, whereais adata classwith avar name. You changeb.name. What isa.name?User b = a;(a class). You changeb.name. What isa.name?const b = a, whereais an object. You changeb.name. What isa.name?Which of these is a value type in Swift?Which of these behaves like a value in Kotlin?Which of these is a value in Java?Which of these is a value in TypeScript?
What are Swift's
String,ArrayandDictionary?Is a Kotlindata classa value type?Is a Javarecorda value type?Does TypeScript have value types besides primitives?When should a Swift model be a
classrather than astruct?How does Kotlin code get value-like behavior from reference types?How does Java code get value-like behavior from reference types?How does TypeScript code get value-like behavior from objects?