Object-Oriented Programming (OOP) Module 1 – Objects and classes
References, identity and aliasing
Why two variables can name one object, what b = a copies in 14 languages, how arguments are passed, and when to test identity instead of equality.
What you will learn
- Predict when two variables share one object and when assignment copies it
- Explain why a function that changes its argument is seen by the caller, while rebinding the argument is not
- Choose an identity test or an equality test in each language
- Avoid the cached-integer traps of Java's == and Python's is
Before you start
What you’ll learn · Before you start
What you will learn
- Predict when two variables share one object and when assignment copies it
- Explain why a function that changes its argument is seen by the caller, while rebinding the argument is not
- Choose an identity test or an equality test in each language
- Avoid the cached-integer traps of Java's == and Python's is
Before you start
On this page
A card holds a balance of 100. The program runs b = a, then b.balance -= 30, and prints a.balance. In Python,
Java and JavaScript it prints 70; in C++, Go and Swift it prints 100. Both answers are correct, because b = a means
different things: either a second name for the same object, or a second object.
Below, that line runs in 14 languages. After it come function arguments, the difference between asking “the same object?” and “equal values?”, and the two caches that make an identity test give the answer an equality test should.
A variable can hold a reference instead of the object
In Python, Java, JavaScript and most languages of this track, a variable that names an object does not contain the object. It holds a reference: A value that leads to an object stored elsewhere. In Python, Java and JavaScript a variable of an object type holds a reference, so copying the variable copies the reference, not the object., a way to reach an object that lives somewhere else. Python’s FAQ says it plainly: “Variables are simply names that refer to objects.” The Java Language Specification says the same in its own terms: reference values “are pointers to these objects”. Step through five lines and watch the arrows:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- a = Card(100). Card(100) makes an object, and a = names it. The name is not the object. a holds a reference, drawn as an arrow from the name to the card.
- b = a. b = a copies the reference, not the card. Two names now point at one object, so b is an alias of a.
- b.balance -= 30. The change goes through b to the only card there is. Reading a.balance now gives 70 as well, although no line mentioned a.
- c = Card(70). Card(70) makes a second object with the same balance, and c names it. Two cards, three names.
- Same object, or equal?. a is b is True, because both arrows end at one card. a is c is False, although a.balance == c.balance is True. Identity asks about the arrows, equality about what they point at.
After b = a there is still only one card, reached by two names: b is an alias: A second name for the same object. After b = a in Python or Java, b is an alias of a, and a change made through either name is seen through both. of a.
Changing the card through b changes what a sees, because there is nothing else for a to see. The new Card(70)
made a second card with an equal balance, which shares nothing with the first.
A reference can also lead to no object at all: null in Java, JavaScript and C#, nil in Go. Python’s None and
Ruby’s nil play the same part, although each is a single object of its own that stands for “no value”. Calling a
method that only a card has, such as pay, then fails while the program runs, with AttributeError,
NullPointerException or TypeError. Rust has no null reference at all: a value that may be missing has the type
Option. Swift’s optionals and Kotlin’s nullable types, such as String?, do the same job, and the compiler makes you
check them first.
Checkpoint
Check: Variables that hold references
Assignment shares a reference or copies a value
Whether b = a gives the object a second name or makes a second object depends on the language and on the kind of
type. The program below writes a card in each language’s most natural way and runs b = a followed by a change
through b. This time the languages do not agree, and the block says why for each one:
Why the outputs differ: The same line b = a gives the card a second name in the languages whose card is a class, and copies it in those whose card is a struct, so a change through b reaches a only in the first group.
Python 3.14 · main.py
class Card:
def __init__(self, balance: int) -> None:
self.balance = balance
a = Card(100)
b = a # copy the card, or give it a second name?
b.balance -= 30
print(f"a.balance = {a.balance}, b.balance = {b.balance}") In Python: b = a never copies in Python. It binds a second name to the object that a names, whatever its type.
What Python printed
a.balance = 70, b.balance = 70
Recorded with CPython 3.14.8 on macOS 26 arm64. It runs in your browser with Pyodide 314.0.7 (CPython 3.14.2). To run it yourself: python3 main.py
JavaScript Node 24 · main.mjs
class Card {
constructor(balance) {
this.balance = balance;
}
}
const a = new Card(100);
const b = a; // copy the card, or give it a second name?
b.balance -= 30;
console.log(`a.balance = ${a.balance}, b.balance = ${b.balance}`); What JavaScript printed
a.balance = 70, b.balance = 70
Recorded with Node.js 24.21.0 LTS (V8 13.6) on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0). To run it yourself: node main.mjs
Java 25 LTS · Main.java
public class Main {
public static void main(String[] args) {
Card a = new Card(100);
Card b = a; // copy the card, or give it a second name?
b.balance -= 30;
System.out.println("a.balance = " + a.balance + ", b.balance = " + b.balance);
}
}
final class Card {
int balance;
Card(int balance) {
this.balance = balance;
}
} In Java: b = a copies the reference, not the object. Java never copies an object unless code asks for it.
What Java printed
a.balance = 70, b.balance = 70
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. It runs in your browser with a TeaVM 0.16.0 build in QuickJS (quickjs-emscripten 0.32.0). To run it yourself: java Main.java
C++23 · main.cpp
#include <print>
struct Card {
int balance;
};
int main() {
Card a{100};
Card b = a; // copy the card, or give it a second name?
b.balance -= 30;
std::println("a.balance = {}, b.balance = {}", a.balance, b.balance);
} Why C++ prints something else: A C++ object is a value, so Card b = a copies every field and a stays at 100. Card& b = a would make b a second name.
What C++ printed
a.balance = 100, b.balance = 70
Recorded with Apple clang 21.0.0 (C++23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.cpp -o main && ./main
C# 14 · .NET 10 · app.cs
var a = new Card(100);
var b = a; // copy the card, or give it a second name?
b.Balance -= 30;
Console.WriteLine($"a.balance = {a.Balance}, b.balance = {b.Balance}");
sealed class Card(int balance)
{
public int Balance { get; set; } = balance;
} In C#: Card is a class, a reference type, so b = a shares it. Declared as a struct, Card would be copied, as in Swift.
What C# printed
a.balance = 70, b.balance = 70
Recorded with C# 14 on .NET 10.0.12 (SDK 10.0.401) on macOS 26 arm64. It runs in your browser with the .NET runtime for WebAssembly 10.0.12. To run it yourself: dotnet run app.cs
C23 · main.c
#include <stdio.h>
struct card {
int balance;
};
int main(void) {
struct card a = {.balance = 100};
struct card b = a; // copy the card, or give it a second name?
b.balance -= 30;
printf("a.balance = %d, b.balance = %d\n", a.balance, b.balance);
return 0;
} Why C prints something else: Assigning a struct copies all of its members, so b is a second card. A pointer, struct card *b = &a, would share it.
What C printed
a.balance = 100, b.balance = 70
Recorded with Apple clang 21.0.0 (C23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.c -o main && ./main
PHP 8.5 · main.php
<?php
declare(strict_types=1);
final class Card
{
public function __construct(public int $balance) {}
}
$a = new Card(100);
$b = $a; // copy the card, or give it a second name?
$b->balance -= 30;
echo "a.balance = {$a->balance}, b.balance = {$b->balance}\n"; In PHP: An object variable holds an identifier of the object, and $b = $a copies the identifier. PHP arrays, unlike objects, are copied by assignment.
What PHP printed
a.balance = 70, b.balance = 70
Recorded with PHP 8.5.11 (CLI) on macOS 26 arm64. It runs in your browser with php-wasm 0.2.0 (PHP 8.5.2). To run it yourself: php main.php
Rust 1.99 · main.rs
#[derive(Clone, Copy)]
struct Card {
balance: i32,
}
fn main() {
let a = Card { balance: 100 };
let mut b = a; // copy the card, or give it a second name?
b.balance -= 30;
println!("a.balance = {}, b.balance = {}", a.balance, b.balance);
} Why Rust prints something else: Card derives Copy, so let b = a copies it. Without Copy the line would move the card, and a could not be used again.
What Rust printed
a.balance = 100, b.balance = 70
Recorded with rustc 1.99.0 (edition 2024) on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: rustc --edition 2024 -O main.rs && ./main
TypeScript 6.0 · main.ts
class Card {
balance: number;
constructor(balance: number) {
this.balance = balance;
}
}
const a = new Card(100);
const b = a; // copy the card, or give it a second name?
b.balance -= 30;
console.log(`a.balance = ${a.balance}, b.balance = ${b.balance}`); What TypeScript printed
a.balance = 70, b.balance = 70
Recorded with TypeScript 6.0.3 (strict), run by Node.js 24.21.0 on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0) after Sucrase 3.35.1. To run it yourself: npx -p typescript@6.0.3 tsc --noEmit --strict main.ts && node main.ts
Go 1.27 · main.go
package main
import "fmt"
type Card struct {
balance int
}
func main() {
a := Card{balance: 100}
b := a // copy the card, or give it a second name?
b.balance -= 30
fmt.Printf("a.balance = %d, b.balance = %d\n", a.balance, b.balance)
} Why Go prints something else: Assigning a struct value copies its fields, so b is a separate card. b := &a would share it through a pointer.
What Go printed
a.balance = 100, b.balance = 70
Recorded with Go 1.27.1 on macOS 26 arm64. It runs in your browser with the Go compiler in WebAssembly 1.27.1. To run it yourself: go run main.go
Swift 6.4 · main.swift
struct Card {
var balance: Int
}
let a = Card(balance: 100)
var b = a // copy the card, or give it a second name?
b.balance -= 30
print("a.balance = \(a.balance), b.balance = \(b.balance)") Why Swift prints something else: Card is a struct, a value type, so var b = a copies it. Declared as a final class, it would be shared.
What Swift printed
a.balance = 100, b.balance = 70
Recorded with Apple Swift 6.4 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: swiftc -O main.swift -o main && ./main
Ruby 4.0 · main.rb
# frozen_string_literal: true
# A card that holds a balance.
class Card
attr_accessor :balance
def initialize(balance)
@balance = balance
end
end
a = Card.new(100)
b = a # copy the card, or give it a second name?
b.balance -= 30
puts "a.balance = #{a.balance}, b.balance = #{b.balance}" What Ruby printed
a.balance = 70, b.balance = 70
Recorded with CRuby 4.0.7 on macOS 26 arm64. It runs in your browser with ruby.wasm 2.10.1 (Ruby 4.0.0). To run it yourself: ruby main.rb
Kotlin 2.4 · Main.kt
class Card(
var balance: Int,
)
fun main() {
val a = Card(100)
val b = a // copy the card, or give it a second name?
b.balance -= 30
println("a.balance = ${a.balance}, b.balance = ${b.balance}")
} In Kotlin: b = a copies the reference. Even a data class, which gives copy(), is shared until copy() is called.
What Kotlin printed
a.balance = 70, b.balance = 70
Recorded with Kotlin 2.4.20 on Java 25.0.4.1 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
Dart 3.13 · main.dart
class Card {
Card(this.balance);
int balance;
}
void main() {
final a = Card(100);
final b = a; // copy the card, or give it a second name?
b.balance -= 30;
print('a.balance = ${a.balance}, b.balance = ${b.balance}');
} What Dart printed
a.balance = 70, b.balance = 70
Recorded with Dart 3.13.5 on macOS 26 arm64. It runs in your browser with a WebAssembly build by dart2wasm 3.13.5. To run it yourself: dart run main.dart
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. Runs on this device: a JavaScript build of this program (about 9 KB), made when the page was built. The first run also downloads the JavaScript runner (about 0.6 MB), which is kept for the next runs. To change the code, copy it and run it with the command below. Runs on this device: a WebAssembly build of this program (about 150 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device: a .NET build of this program (about 2 KB), made when the page was built. The first run also downloads .NET (about 3.6 MB), which is kept for the next runs. Edit runs your changes in the browser: the first edited run downloads .NET (about 8.9 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads .NET (about 8.9 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 8 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads PHP (about 4.3 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 19 KB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device, in your browser. The first run downloads the JavaScript runner (about 0.9 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 714 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the Go compiler (about 18.7 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads the Go compiler (about 18.7 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 1.7 MB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device, in your browser. The first run downloads Ruby (about 10.2 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 179 KB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device: a WebAssembly build of this program (about 9 KB), made when the page was built. To change the code, copy it and run it with the command below.Your run, in this browser
Nine languages printed 70 for a, and five printed 100. In the nine, the card is a class, and a variable of a class
type holds a reference, so b = a copied the reference. In C, C++, Go and Swift the card is a struct, and assigning a
struct copies all of its fields; Rust’s card copies too, but only because it was declared Copy. Without that, Rust
moves the value: after let b = a;, the compiler refuses any later use of a. The table sums up the rule of each
language and how to get the other behaviour:
| Language | b = a for its usual record type |
How to get the other behaviour |
|---|---|---|
| Python, Ruby, JavaScript, TypeScript, PHP, Dart | Shares: every object variable is a reference | Copy explicitly: copy.copy(a), a.dup, clone $a, or a copy method |
| Java, Kotlin | Shares objects; Java copies primitives such as int |
A copy constructor, or copy() of a Kotlin data class |
| C# | Shares a class; copies a struct | Choose struct or class |
| Swift | Shares a class; copies a struct | Choose struct or class |
| C++, C, Go | Copies the whole value | Use a reference or a pointer: Card& b = a, &a |
| Rust | Moves the value, or copies a Copy type |
Borrow it: let b = &mut a |
A backup that is not a backup
backup = marks before changing marks keeps nothing in Python, Java or JavaScript: both names reach the same
list, so the “backup” changes with it. Copy the list itself: marks.copy() in Python, new ArrayList<>(marks) in
Java, [...marks] in JavaScript.
Checkpoint
Check: What assignment shares or copies
Arguments are passed by value, and the value may be a reference
A function receives its arguments the way assignment hands out values: Python’s FAQ says arguments “are passed by assignment”. For an object, what the function receives is a copy of the caller’s reference. Two things can then happen inside it, with opposite results for the caller. The function can change the object through its copy of the reference, which the caller sees, or it can point its own parameter at a different object, which the caller never sees:
Python 3.14 · main.py
class Card:
def __init__(self, balance: int) -> None:
self.balance = balance
def charge_fee(card: Card) -> None:
card.balance -= 10 # changes the object the caller passed in
def swap_card(card: Card) -> int:
card = Card(card.balance + 400) # points this function's own name at a new card
return card.balance
mine = Card(100)
charge_fee(mine)
print(f"after charge_fee(mine): {mine.balance}")
inside = swap_card(mine)
print(f"swap_card(mine) made a card of {inside}; mine is still {mine.balance}")
alias = mine
twin = Card(mine.balance)
print(f"alias and mine: {'same object' if alias is mine else 'different objects'}")
print(f"twin and mine: {'same object' if twin is mine else 'different objects'}")
print(f"equal balances: {'yes' if twin.balance == mine.balance else 'no'}") In Python: Python passes arguments by assignment. card is a new name bound to the caller's object, so card = Card(…) rebinds that name only, and is tests identity.
Recorded with CPython 3.14.8 on macOS 26 arm64. It runs in your browser with Pyodide 314.0.7 (CPython 3.14.2). To run it yourself: python3 main.py
JavaScript Node 24 · main.mjs
class Card {
constructor(balance) {
this.balance = balance;
}
}
function chargeFee(card) {
card.balance -= 10; // changes the object the caller passed in
}
function swapCard(card) {
card = new Card(card.balance + 400); // points this function's own name at a new card
return card.balance;
}
const mine = new Card(100);
chargeFee(mine);
console.log(`after charge_fee(mine): ${mine.balance}`);
const inside = swapCard(mine);
console.log(`swap_card(mine) made a card of ${inside}; mine is still ${mine.balance}`);
const alias = mine;
const twin = new Card(mine.balance);
console.log(`alias and mine: ${alias === mine ? "same object" : "different objects"}`);
console.log(`twin and mine: ${twin === mine ? "same object" : "different objects"}`);
console.log(`equal balances: ${twin.balance === mine.balance ? "yes" : "no"}`); In JavaScript: === on two objects is true only when both sides refer to the same object, whatever their fields hold.
Recorded with Node.js 24.21.0 LTS (V8 13.6) on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0). To run it yourself: node main.mjs
Java 25 LTS · Main.java
public class Main {
static void chargeFee(Card card) {
card.balance -= 10; // changes the object the caller passed in
}
static int swapCard(Card card) {
card = new Card(card.balance + 400); // points this function's own name at a new card
return card.balance;
}
public static void main(String[] args) {
Card mine = new Card(100);
chargeFee(mine);
System.out.println("after charge_fee(mine): " + mine.balance);
int inside = swapCard(mine);
System.out.println(
"swap_card(mine) made a card of " + inside + "; mine is still " + mine.balance);
Card alias = mine;
Card twin = new Card(mine.balance);
System.out.println("alias and mine: " + (alias == mine ? "same object" : "different objects"));
System.out.println("twin and mine: " + (twin == mine ? "same object" : "different objects"));
System.out.println("equal balances: " + (twin.balance == mine.balance ? "yes" : "no"));
}
}
final class Card {
int balance;
Card(int balance) {
this.balance = balance;
}
} In Java: Java passes every argument by value, and for an object the value is a reference. card = new Card(…) changes the parameter, never the caller's variable.
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. It runs in your browser with a TeaVM 0.16.0 build in QuickJS (quickjs-emscripten 0.32.0). To run it yourself: java Main.java
C++23 · main.cpp
#include <print>
struct Card {
int balance;
};
void charge_fee(Card *card) {
card->balance -= 10; // changes the object the caller passed in
}
int swap_card(const Card *card) {
const Card fresh{card->balance + 400};
card = &fresh; // points this function's own name at a new card
return card->balance;
}
int main() {
Card mine{100};
charge_fee(&mine);
std::println("after charge_fee(mine): {}", mine.balance);
const int inside = swap_card(&mine);
std::println("swap_card(mine) made a card of {}; mine is still {}", inside,
mine.balance);
const Card *alias = &mine;
const Card twin{mine.balance};
std::println("alias and mine: {}",
alias == &mine ? "same object" : "different objects");
std::println("twin and mine: {}",
&twin == &mine ? "same object" : "different objects");
std::println("equal balances: {}",
twin.balance == mine.balance ? "yes" : "no");
} In C++: The functions take pointers, which is what Java and Python pass. A reference parameter, Card& card, would be a true alias, and assigning to it would overwrite mine.
Recorded with Apple clang 21.0.0 (C++23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.cpp -o main && ./main
C# 14 · .NET 10 · app.cs
var mine = new Card(100);
ChargeFee(mine);
Console.WriteLine($"after charge_fee(mine): {mine.Balance}");
int inside = SwapCard(mine);
Console.WriteLine($"swap_card(mine) made a card of {inside}; mine is still {mine.Balance}");
var alias = mine;
var twin = new Card(mine.Balance);
Console.WriteLine($"alias and mine: {Sameness(ReferenceEquals(alias, mine))}");
Console.WriteLine($"twin and mine: {Sameness(ReferenceEquals(twin, mine))}");
Console.WriteLine($"equal balances: {(twin.Balance == mine.Balance ? "yes" : "no")}");
static void ChargeFee(Card card)
{
card.Balance -= 10; // changes the object the caller passed in
}
static string Sameness(bool same) => same ? "same object" : "different objects";
static int SwapCard(Card card)
{
card = new Card(card.Balance + 400); // points this function's own name at a new card
return card.Balance;
}
sealed class Card(int balance)
{
public int Balance { get; set; } = balance;
} In C#: The parameter holds a copy of the reference. Declared ref Card card, it would be the caller's variable itself, and card = new Card(…) would replace mine.
Recorded with C# 14 on .NET 10.0.12 (SDK 10.0.401) on macOS 26 arm64. It runs in your browser with the .NET runtime for WebAssembly 10.0.12. To run it yourself: dotnet run app.cs
C23 · main.c
#include <stdio.h>
struct card {
int balance;
};
static void charge_fee(struct card *card) {
card->balance -= 10; // changes the object the caller passed in
}
static int swap_card(const struct card *card) {
const struct card fresh = {.balance = card->balance + 400};
card = &fresh; // points this function's own name at a new card
return card->balance;
}
int main(void) {
struct card mine = {.balance = 100};
charge_fee(&mine);
printf("after charge_fee(mine): %d\n", mine.balance);
const int inside = swap_card(&mine);
printf("swap_card(mine) made a card of %d; mine is still %d\n", inside,
mine.balance);
const struct card *alias = &mine;
const struct card twin = {.balance = mine.balance};
printf("alias and mine: %s\n",
alias == &mine ? "same object" : "different objects");
printf("twin and mine: %s\n",
&twin == &mine ? "same object" : "different objects");
printf("equal balances: %s\n", twin.balance == mine.balance ? "yes" : "no");
return 0;
} In C: C passes the pointer itself by value, so card = &fresh changes only the copy inside swap_card, and comparing pointers is the identity test.
Recorded with Apple clang 21.0.0 (C23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.c -o main && ./main
PHP 8.5 · main.php
<?php
declare(strict_types=1);
final class Card
{
public function __construct(public int $balance) {}
}
function chargeFee(Card $card): void
{
$card->balance -= 10; // changes the object the caller passed in
}
function swapCard(Card $card): int
{
$card = new Card($card->balance + 400); // points this function's own name at a new card
return $card->balance;
}
$mine = new Card(100);
chargeFee($mine);
echo "after charge_fee(mine): {$mine->balance}\n";
$inside = swapCard($mine);
echo "swap_card(mine) made a card of {$inside}; mine is still {$mine->balance}\n";
$alias = $mine;
$twin = new Card($mine->balance);
echo 'alias and mine: ' . ($alias === $mine ? 'same object' : 'different objects') . "\n";
echo 'twin and mine: ' . ($twin === $mine ? 'same object' : 'different objects') . "\n";
echo 'equal balances: ' . ($twin->balance === $mine->balance ? 'yes' : 'no') . "\n"; In PHP: The parameter receives a copy of the object's identifier. PHP's &$card would pass the caller's variable itself, like a C++ reference.
Recorded with PHP 8.5.11 (CLI) on macOS 26 arm64. It runs in your browser with php-wasm 0.2.0 (PHP 8.5.2). To run it yourself: php main.php
Rust 1.99 · main.rs
struct Card {
balance: i32,
}
fn charge_fee(card: &mut Card) {
card.balance -= 10; // changes the object the caller passed in
}
fn swap_card(card: &Card) -> i32 {
let fresh = Card {
balance: card.balance + 400,
};
let card = &fresh; // points this function's own name at a new card
card.balance
}
fn main() {
let mut mine = Card { balance: 100 };
charge_fee(&mut mine);
println!("after charge_fee(mine): {}", mine.balance);
let inside = swap_card(&mine);
println!(
"swap_card(mine) made a card of {inside}; mine is still {}",
mine.balance
);
let alias = &mine;
let twin = Card {
balance: mine.balance,
};
let same = |x: &Card| {
if std::ptr::eq(x, &mine) {
"same object"
} else {
"different objects"
}
};
println!("alias and mine: {}", same(alias));
println!("twin and mine: {}", same(&twin));
let equal = if twin.balance == mine.balance {
"yes"
} else {
"no"
};
println!("equal balances: {equal}");
} In Rust: A borrowed parameter cannot point at a value made inside the function, so swap_card shadows card with a new binding to fresh. std::ptr::eq compares addresses.
Recorded with rustc 1.99.0 (edition 2024) on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: rustc --edition 2024 -O main.rs && ./main
TypeScript 6.0 · main.ts
class Card {
balance: number;
constructor(balance: number) {
this.balance = balance;
}
}
function chargeFee(card: Card): void {
card.balance -= 10; // changes the object the caller passed in
}
function swapCard(card: Card): number {
card = new Card(card.balance + 400); // points this function's own name at a new card
return card.balance;
}
const mine = new Card(100);
chargeFee(mine);
console.log(`after charge_fee(mine): ${mine.balance}`);
const inside = swapCard(mine);
console.log(`swap_card(mine) made a card of ${inside}; mine is still ${mine.balance}`);
const alias = mine;
const twin = new Card(mine.balance);
console.log(`alias and mine: ${alias === mine ? "same object" : "different objects"}`);
console.log(`twin and mine: ${twin === mine ? "same object" : "different objects"}`);
console.log(`equal balances: ${twin.balance === mine.balance ? "yes" : "no"}`); Recorded with TypeScript 6.0.3 (strict), run by Node.js 24.21.0 on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0) after Sucrase 3.35.1. To run it yourself: npx -p typescript@6.0.3 tsc --noEmit --strict main.ts && node main.ts
Go 1.27 · main.go
package main
import "fmt"
type Card struct {
balance int
}
func chargeFee(card *Card) {
card.balance -= 10 // changes the object the caller passed in
}
func swapCard(card *Card) int {
card = &Card{balance: card.balance + 400} // points this function's own name at a new card
return card.balance
}
func sameOrNot(same bool) string {
if same {
return "same object"
}
return "different objects"
}
func main() {
mine := &Card{balance: 100}
chargeFee(mine)
fmt.Printf("after charge_fee(mine): %d\n", mine.balance)
inside := swapCard(mine)
fmt.Printf("swap_card(mine) made a card of %d; mine is still %d\n", inside, mine.balance)
alias := mine
twin := &Card{balance: mine.balance}
fmt.Printf("alias and mine: %s\n", sameOrNot(alias == mine))
fmt.Printf("twin and mine: %s\n", sameOrNot(twin == mine))
equal := "no"
if twin.balance == mine.balance {
equal = "yes"
}
fmt.Printf("equal balances: %s\n", equal)
} In Go: Go passes everything by value, so card is a copy of the caller's pointer and pointing it at a new Card leaves mine alone. == on pointers tests identity.
Recorded with Go 1.27.1 on macOS 26 arm64. It runs in your browser with the Go compiler in WebAssembly 1.27.1. To run it yourself: go run main.go
Swift 6.4 · main.swift
final class Card {
var balance: Int
init(balance: Int) {
self.balance = balance
}
}
func chargeFee(_ card: Card) {
card.balance -= 10 // changes the object the caller passed in
}
func swapCard(_ card: Card) -> Int {
var card = card
card = Card(balance: card.balance + 400) // points this function's own name at a new card
return card.balance
}
let mine = Card(balance: 100)
chargeFee(mine)
print("after charge_fee(mine): \(mine.balance)")
let inside = swapCard(mine)
print("swap_card(mine) made a card of \(inside); mine is still \(mine.balance)")
let alias = mine
let twin = Card(balance: mine.balance)
print("alias and mine: \(alias === mine ? "same object" : "different objects")")
print("twin and mine: \(twin === mine ? "same object" : "different objects")")
print("equal balances: \(twin.balance == mine.balance ? "yes" : "no")") In Swift: Card is a class here, so it is shared. Parameters are constants, so swapCard copies the reference into var card before pointing it elsewhere; === tests identity.
Recorded with Apple Swift 6.4 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: swiftc -O main.swift -o main && ./main
Ruby 4.0 · main.rb
# frozen_string_literal: true
# A card that holds a balance.
class Card
attr_accessor :balance
def initialize(balance)
@balance = balance
end
end
def charge_fee(card)
card.balance -= 10 # changes the object the caller passed in
end
def swap_card(card)
card = Card.new(card.balance + 400) # points this function's own name at a new card
card.balance
end
mine = Card.new(100)
charge_fee(mine)
puts "after charge_fee(mine): #{mine.balance}"
inside = swap_card(mine)
puts "swap_card(mine) made a card of #{inside}; mine is still #{mine.balance}"
alias_name = mine
twin = Card.new(mine.balance)
puts "alias and mine: #{alias_name.equal?(mine) ? 'same object' : 'different objects'}"
puts "twin and mine: #{twin.equal?(mine) ? 'same object' : 'different objects'}"
puts "equal balances: #{twin.balance == mine.balance ? 'yes' : 'no'}" In Ruby: Ruby passes references by value, as Python does. alias is a keyword in Ruby, so the second name is alias_name, and equal? tests identity.
Recorded with CRuby 4.0.7 on macOS 26 arm64. It runs in your browser with ruby.wasm 2.10.1 (Ruby 4.0.0). To run it yourself: ruby main.rb
Kotlin 2.4 · Main.kt
class Card(
var balance: Int,
)
fun chargeFee(card: Card) {
card.balance -= 10 // changes the object the caller passed in
}
fun swapCard(card: Card): Int {
var current = card
current = Card(current.balance + 400) // points this function's own name at a new card
return current.balance
}
fun main() {
val mine = Card(100)
chargeFee(mine)
println("after charge_fee(mine): ${mine.balance}")
val inside = swapCard(mine)
println("swap_card(mine) made a card of $inside; mine is still ${mine.balance}")
val alias = mine
val twin = Card(mine.balance)
println("alias and mine: ${if (alias === mine) "same object" else "different objects"}")
println("twin and mine: ${if (twin === mine) "same object" else "different objects"}")
println("equal balances: ${if (twin.balance == mine.balance) "yes" else "no"}")
} In Kotlin: Parameters are read-only, so swapCard copies the reference into var current before pointing it at a new card. === tests identity and == equality.
Recorded with Kotlin 2.4.20 on Java 25.0.4.1 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
Dart 3.13 · main.dart
class Card {
Card(this.balance);
int balance;
}
void chargeFee(Card card) {
card.balance -= 10; // changes the object the caller passed in
}
int swapCard(Card card) {
card = Card(
card.balance + 400,
); // points this function's own name at a new card
return card.balance;
}
void main() {
final mine = Card(100);
chargeFee(mine);
print('after charge_fee(mine): ${mine.balance}');
final inside = swapCard(mine);
print(
'swap_card(mine) made a card of $inside; mine is still ${mine.balance}',
);
final alias = mine;
final twin = Card(mine.balance);
print(
'alias and mine: ${identical(alias, mine) ? 'same object' : 'different objects'}',
);
print(
'twin and mine: ${identical(twin, mine) ? 'same object' : 'different objects'}',
);
print('equal balances: ${twin.balance == mine.balance ? 'yes' : 'no'}');
} In Dart: Reassigning a parameter changes only the function's own variable. identical() tests whether two references point to one object.
Recorded with Dart 3.13.5 on macOS 26 arm64. It runs in your browser with a WebAssembly build by dart2wasm 3.13.5. To run it yourself: dart run main.dart
What every language printed
after charge_fee(mine): 90 swap_card(mine) made a card of 490; mine is still 90 alias and mine: same object twin and mine: different objects equal balances: yes
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. Runs on this device: a JavaScript build of this program (about 9 KB), made when the page was built. The first run also downloads the JavaScript runner (about 0.6 MB), which is kept for the next runs. To change the code, copy it and run it with the command below. Runs on this device: a WebAssembly build of this program (about 150 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device: a .NET build of this program (about 2 KB), made when the page was built. The first run also downloads .NET (about 3.6 MB), which is kept for the next runs. Edit runs your changes in the browser: the first edited run downloads .NET (about 8.9 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads .NET (about 8.9 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 8 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads PHP (about 4.3 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 19 KB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device, in your browser. The first run downloads the JavaScript runner (about 0.9 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 716 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the Go compiler (about 18.7 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads the Go compiler (about 18.7 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 1.7 MB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device, in your browser. The first run downloads Ruby (about 10.2 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 179 KB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device: a WebAssembly build of this program (about 9 KB), made when the page was built. To change the code, copy it and run it with the command below.Your run, in this browser
charge_fee took 10 off the card the caller passed, and the caller saw 90. swap_card pointed its parameter at a new
card of 490 and returned that balance, but mine was still 90: the function only ever had a copy of the reference.
Java’s tutorial puts the rule the same way: object arguments “are also passed into methods by value”, so the passed-in
reference “still references the same object as before”. The PHP manual adds that object variables are not aliases but
“hold a copy of the identifier, which points to the same object”.
Some languages also offer true pass-by-reference, in which the function receives the caller’s variable itself:
Card& card in C++, ref Card card in C#, &$card in PHP. Then card = … inside the function replaces the caller’s
card, exactly what swap_card could not do. The notes under the C++, C# and PHP tabs show where that would change the
output.
Key idea
A function receives a copy of the caller’s reference: changing the object through it is seen by the caller, while pointing the parameter at another object is not.
Checkpoint
Check: How arguments are passed
Same object or equal values: two different questions
The last three lines of the program asked two questions about mine, alias and twin. Is it the same object? Do
the two hold equal values? alias and mine are one object; twin is a different object whose balance happens to be
equal. Every language has a way to ask each question:
| Language | Same object? | Equal values? |
|---|---|---|
| Python | a is b |
a == b |
| Java | a == b |
a.equals(b) |
| JavaScript, TypeScript | a === b |
no built-in test; compare the fields |
| C# | ReferenceEquals(a, b) |
a.Equals(b), == when the type defines it |
| C++, C | &a == &b, or two pointers with == |
a == b when operator== is defined (C++) |
| Go | p == q on two pointers |
a == b on two struct values |
| Rust | std::ptr::eq(a, b) |
a == b with PartialEq |
| Swift, Kotlin | a === b |
a == b |
| Ruby | a.equal?(b) |
a == b |
| PHP | $a === $b |
$a == $b |
| Dart | identical(a, b) |
a == b |
For a class that defines no equality of its own, most languages fall back to identity: Python’s ==, Java’s equals
and C#’s Equals all compare references until the class says otherwise. That is why the program compared the balances
rather than the cards. Teaching a class what “equal” means, and keeping its hash code consistent with that, is the
subject of a later module.
Which question to ask follows from the problem. Identity answers “is this the very object I stored?”, as when a list of open files must not hold the same file twice. Equality answers “do these mean the same thing?”, as for two prices, two dates or two strings, and it is the question most code needs.
Checkpoint
Check: Identity and equality
Cached small values make identity tests lie
Numbers and strings are where identity tests go wrong most often, because some runtimes keep one shared object for
popular values. Java’s boxing turns an int into an Integer object, and the Java Language Specification guarantees
that boxing the same constant from -128 to 127 always gives the identical object, while it “disallows any
assumptions” about other values. Run on the pinned JDK, == happens to be right at 127 and wrong at 128:
public class IntegerCache {
public static void main(String[] args) {
// Boxing turns an int into an Integer object; small values come from a cache.
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println("127: a == b is " + (a == b) + ", a.equals(b) is " + a.equals(b));
System.out.println("128: c == d is " + (c == d) + ", c.equals(d) is " + c.equals(d));
}
} Output
127: a == b is true, a.equals(b) is true 128: c == d is false, c.equals(d) is true
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java IntegerCache.java
equals was right both times. CPython does something similar for small integers, as an implementation detail of its C
code: version 3.14 keeps one object for each integer from -5 to 256. The program builds each number while it runs,
with int("256"), so that no constant in the source can be shared:
# int(text) builds each number while the program runs, so no constant is shared.
for text in ["256", "257"]:
a = int(text)
b = int(text)
print(f"{text}: a == b is {a == b}, a is b is {a is b}") Output
256: a == b is True, a is b is True 257: a == b is True, a is b is False
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 int_cache.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
At 256, a is b is True, and at 257 it is False, while a == b is True both times. Press Edit and try "-6" and
"-5": the cache starts at -5.
Python 3.15
The Python 3.15 documentation gives CPython’s cache as the integers from -5 to 1024, so the same program prints True
for 257 there. A result that changes between versions was never a rule: compare numbers and strings with ==. Since
Python 3.8 the compiler even warns, with a SyntaxWarning, when is is used with a number or string literal.
Checkpoint
Check: Caches that fool identity tests
Practise: stop a function from changing its caller’s list
The exercise gives you a function that returns the right numbers and quietly damages its caller’s data: it writes the new marks into the list it was given, through the reference it received. Make it build a new list instead. The sample tests run in your browser and compare the caller’s list before and after the call:
Exercise · Easy · Python, JavaScript
Return new marks without changing the caller's list
with_bonus(marks, bonus) (JavaScript: withBonus(marks, bonus)) should return the marks with bonus added to each, none above 100: with_bonus([72, 95, 40], 10) is [82, 100, 50]. The starter code returns the right numbers, but it writes them into the list it was given, so the caller's own list changes too, through the reference the function received.
Fix it so that:
- the result is a new list with every mark plus
bonus, capped at 100; - the caller's list is exactly as it was, and the result is not that same list;
- an empty list gives an empty list.
The sample tests check the values, keep a reference to the caller's list and compare it before and after the call, and check that the result and the argument are different objects.
Python · Starter code · bonus.py
def with_bonus(marks: list[int], bonus: int) -> list[int]:
"""The marks with bonus added to each, at most 100. The caller's list must not change."""
for i in range(len(marks)):
marks[i] = min(100, marks[i] + bonus)
return marks The sample tests · test_bonus.py
from bonus import with_bonus
def test_values():
"""every mark gets the bonus, and none goes above 100"""
assert with_bonus([72, 95, 40], 10) == [82, 100, 50]
assert with_bonus([100, 0], 5) == [100, 5]
assert with_bonus([], 10) == [], "an empty list gives an empty list"
def test_callers_list_is_unchanged():
"""the caller's list holds the same marks after the call"""
marks = [72, 95, 40]
before = list(marks)
with_bonus(marks, 10)
assert marks == before, f"the caller's list changed from {before} to {marks}"
def test_result_is_a_new_list():
"""the result is a different list object from the argument"""
marks = [60, 70]
result = with_bonus(marks, 5)
assert result is not marks, "the function returned the caller's own list"
result.append(99)
assert marks == [60, 70], "changing the result must not change the caller's list" JavaScript · Starter code · bonus.mjs
/** The marks with bonus added to each, at most 100. The caller's array must not change. */
export function withBonus(marks, bonus) {
for (let i = 0; i < marks.length; i++) {
marks[i] = Math.min(100, marks[i] + bonus);
}
return marks;
} The sample tests · bonus.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { withBonus } from './bonus.mjs';
test('every mark gets the bonus, and none goes above 100', () => {
assert.deepEqual(withBonus([72, 95, 40], 10), [82, 100, 50]);
assert.deepEqual(withBonus([100, 0], 5), [100, 5]);
assert.deepEqual(withBonus([], 10), [], 'an empty array gives an empty array');
});
test("the caller's array holds the same marks after the call", () => {
const marks = [72, 95, 40];
const before = [...marks];
withBonus(marks, 10);
assert.deepEqual(marks, before, "the caller's array changed");
});
test('the result is a different array from the argument', () => {
const marks = [60, 70];
const result = withBonus(marks, 5);
assert.ok(result !== marks, "the function returned the caller's own array");
result.push(99);
assert.deepEqual(marks, [60, 70], "changing the result must not change the caller's array");
}); A hint
Build the answer in a list of your own instead of assigning to marks[i]: start from an empty list and append each new mark, or use a comprehension (JavaScript: marks.map(...)). Reading the caller's list is fine; writing to it is what the caller can see.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
The questions cover all five ideas. The weakest one comes first, and every right answer makes the next question a little harder:
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · Is Java pass by value or pass by reference?
Java passes every argument by value, with no exceptions. An int argument arrives as a copy of the number, and an
object argument arrives as a copy of the reference to the object, so the method holds its own reference. Two things
follow. The method can change the object through the copy, card.balance -= 10, and the caller sees the change,
because both references point at one object. The method cannot make the caller’s variable point at another object:
card = new Card(500) changes only its own copy, which is why a swap(a, b) method that makes the caller’s two
variables trade objects is impossible in Java. In the lesson’s program, charge_fee left mine at 90 and swap_card could not replace it.
“Pass by reference” would mean the method receives the caller’s variable itself, as C++’s Card& or C#’s ref do.
Three follow-ups an interviewer might ask next, each with four answers to weigh:
Checkpoint
Interview round: three follow-up questions
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A helper normalise(marks) is meant to return a cleaned copy of a list of marks, yet after it runs in production the caller's own list has changed. How do you find and fix the bug, and how do you stop it from coming back?
-
Fresher to mid
A Java service compares order IDs held as Integer objects with ==. Every test passes, and in production equal IDs are reported as different. Explain why, fix it, and say how Java, Python and JavaScript separate identity from equality.
Key takeaways
- A variable of a class type holds a reference: after
b = athe names are aliases of one object, and a change through either is seen through both. - Whether
b = ashares or copies depends on the language and the type: classes share in Python, Java, JavaScript, C# and Kotlin, while the structs of C, C++, Go and Swift are copied and Rust moves its values unless they areCopy. - Arguments are passed by value, and for an object that value is a reference: a function can change the caller’s
object but cannot make the caller’s variable point at a new one, as
swap_cardshowed. - Identity asks whether two names reach the same object and equality whether two values mean the same thing; each language spells the two tests differently, and most code needs equality.
- Caches make identity look like equality for small values, Java’s
Integerfrom -128 to 127 and CPython’s integers from -5 to 256 in 3.14, so compare numbers and strings withequalsor==.
Terms in this lesson
- alias
- A second name for the same object. After b = a in Python or Java, b is an alias of a, and a change made through either name is seen through both.
- reference
- A value that leads to an object stored elsewhere. In Python, Java and JavaScript a variable of an object type holds a reference, so copying the variable copies the reference, not the object.
References
- Python FAQ: why did changing list y also change list x, and functions with output parameters (Python Software Foundation)
- The Python Language Reference: Data model (objects, values and types; None) (Python Software Foundation)
- Class NilClass (Ruby documentation): the singleton object nil (Ruby)
- Python/C API, integer objects (the small-integer cache), Python 3.14 (Python Software Foundation)
- Python/C API, integer objects (the small-integer cache), Python 3.15 (Python Software Foundation)
- What's New In Python 3.8: SyntaxWarning for is with a literal (Python Software Foundation)
- Learn Java: Calling methods and constructors (passing reference data type arguments) (Oracle)
- The Java Language Specification, Java SE 25: 4.3.1 Objects (Oracle)
- The Java Language Specification, Java SE 25: 5.1.7 Boxing conversion (Oracle)
- Strict equality (===) (MDN Web Docs)
- Value types (C# reference) (Microsoft)
- The Swift Programming Language: Structures and Classes (value types, reference types, identity operators) (Swift.org)
- The Rust Programming Language: What is ownership? (move and copy) (The Rust Project)
- Go FAQ: when are function parameters passed by value? (The Go Authors)
- C++ FAQ: references (Standard C++ Foundation)
- PHP manual: objects and references (The PHP Group)
- Kotlin: equality (structural and referential) (JetBrains)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress