Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Object-Oriented Programming (OOP)  Module 1 – Objects and classes

Classes, objects, fields and methods

How a class lists the fields each object holds and the methods all of them share, what self and this are, and one class written in 14 languages.

  • Beginner
  • 15 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, Java temurin-25.0.4+101.0.LTS and quickjs 0.32.0
  • By MySmartCoPilot
Free: 15 minutes

What you will learn

  • Define a class with fields, a constructor and methods, and create two objects from it
  • Distinguish what belongs to the class from what belongs to each object
  • Explain what self and this refer to while a method runs
  • Predict what each language does with a field that no constructor set
  • Explain how struct and class differ in C++, C# and Swift

Before you start

What you’ll learn · Before you start

What you will learn

  • Define a class with fields, a constructor and methods, and create two objects from it
  • Distinguish what belongs to the class from what belongs to each object
  • Explain what self and this refer to while a method runs
  • Predict what each language does with a field that no constructor set
  • Explain how struct and class differ in C++, C# and Swift

Before you start

On this page

Two cinema tickets cost 9.00 each, for seats C7 and C8. Upgrading the first to seat A1 must change that ticket and leave the other alone, and a class: A definition of a kind of object: the fields every object of that kind holds and the methods they all share. Ticket is a class; each ticket made from it is an instance. makes that the natural outcome: it lists the fields every ticket has and the methods every ticket can run, and each object made from it keeps values of its own.

Below, one class is written in 14 languages. You will see where each language keeps the fields and where it keeps the methods, follow self and this through a call, and see what each language does with a field that nobody set.

A class describes the fields, and each object holds its own

The Ticket class below says two things about every ticket. It has a seat and a price, kept in cents so that 900 means 9.00, and it can describe itself and upgrade to another seat. No ticket exists until the program asks the class for one: each call of the constructor: The code that runs when a new object is made and gives its fields their first values: __init__ in Python, constructor in JavaScript, a method named like the class in Java and C++, init in Swift. makes a new instance: One object made from a class by its constructor. Instances share the class's methods and each holds its own values for the class's fields. with fields of its own. The program makes two tickets, upgrades one and describes both:

One program · 14 languages Two tickets made from one class

Python 3.14 · main.py

class Ticket:
    def __init__(self, seat: str, price: int) -> None:
        # Each new object gets its own seat and price; the price is in cents.
        self.seat = seat
        self.price = price

    def describe(self) -> str:
        return f"seat {self.seat}, {self.price // 100}.{self.price % 100:02d}"

    def upgrade(self, seat: str, extra: int) -> None:
        self.seat = seat
        self.price += extra


a = Ticket("C7", 900)
b = Ticket("C8", 900)
print(f"a: {a.describe()}")
print(f"b: {b.describe()}")
print("upgrade a to A1, 6.50 more")
a.upgrade("A1", 650)
print(f"a: {a.describe()}")
print(f"b: {b.describe()}")

In Python: self is written out as the first parameter of every method, and a.describe() passes a as self. The fields come into being when __init__ assigns them; nothing declares them.

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 Ticket {
  constructor(seat, price) {
    // Each new object gets its own seat and price; the price is in cents.
    this.seat = seat;
    this.price = price;
  }

  describe() {
    const cents = String(this.price % 100).padStart(2, "0");
    return `seat ${this.seat}, ${Math.trunc(this.price / 100)}.${cents}`;
  }

  upgrade(seat, extra) {
    this.seat = seat;
    this.price += extra;
  }
}

const a = new Ticket("C7", 900);
const b = new Ticket("C8", 900);
console.log(`a: ${a.describe()}`);
console.log(`b: ${b.describe()}`);
console.log("upgrade a to A1, 6.50 more");
a.upgrade("A1", 650);
console.log(`a: ${a.describe()}`);
console.log(`b: ${b.describe()}`);

In JavaScript: The constructor runs when new Ticket(…) is evaluated, with this as the new object, and the fields appear when it assigns them.

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

import java.util.Locale;

public class Main {
  public static void main(String[] args) {
    Locale.setDefault(Locale.ROOT);
    Ticket a = new Ticket("C7", 900);
    Ticket b = new Ticket("C8", 900);
    System.out.println("a: " + a.describe());
    System.out.println("b: " + b.describe());
    System.out.println("upgrade a to A1, 6.50 more");
    a.upgrade("A1", 650);
    System.out.println("a: " + a.describe());
    System.out.println("b: " + b.describe());
  }
}

final class Ticket {
  private String seat;
  private int price;

  Ticket(String seat, int price) {
    // Each new object gets its own seat and price; the price is in cents.
    this.seat = seat;
    this.price = price;
  }

  String describe() {
    return String.format("seat %s, %d.%02d", seat, price / 100, price % 100);
  }

  void upgrade(String seat, int extra) {
    this.seat = seat;
    price += extra;
  }
}

In Java: this.seat = seat is needed because the parameter seat hides the field of the same name. String.format runs after main sets Locale.ROOT, so the digits are the same in every locale.

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 <format>
#include <print>
#include <string>
#include <utility>

class Ticket {
public:
  // Each new object gets its own seat and price; the price is in cents.
  Ticket(std::string seat, int price) : seat_(std::move(seat)), price_(price) {}

  std::string describe() const {
    return std::format("seat {}, {}.{:02}", seat_, price_ / 100, price_ % 100);
  }

  void upgrade(std::string seat, int extra) {
    seat_ = std::move(seat);
    price_ += extra;
  }

private:
  std::string seat_;
  int price_;
};

int main() {
  Ticket a("C7", 900);
  Ticket b("C8", 900);
  std::println("a: {}", a.describe());
  std::println("b: {}", b.describe());
  std::println("upgrade a to A1, 6.50 more");
  a.upgrade("A1", 650);
  std::println("a: {}", a.describe());
  std::println("b: {}", b.describe());
}

In C++: The member initialiser list, : seat_(…), price_(…), sets the fields before the body of the constructor runs. describe() is const: it promises not to change the object.

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

using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

var a = new Ticket("C7", 900);
var b = new Ticket("C8", 900);
Console.WriteLine($"a: {a.Describe()}");
Console.WriteLine($"b: {b.Describe()}");
Console.WriteLine("upgrade a to A1, 6.50 more");
a.Upgrade("A1", 650);
Console.WriteLine($"a: {a.Describe()}");
Console.WriteLine($"b: {b.Describe()}");

// Each new object gets its own seat and price; the price is in cents.
sealed class Ticket(string seat, int price)
{
    public string Seat { get; private set; } = seat;
    public int Price { get; private set; } = price;

    public string Describe() => $"seat {Seat}, {Price / 100}.{Price % 100:D2}";

    public void Upgrade(string seat, int extra)
    {
        Seat = seat;
        Price += extra;
    }
}

In C#: Ticket(string seat, int price) is a primary constructor, new in C# 12, whose parameters initialise the two properties. {Price % 100:D2} pads the cents to two digits.

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 <stddef.h>
#include <stdio.h>

struct ticket {
  char seat[8];
  int price;
};

static struct ticket ticket_new(const char *seat, int price) {
  // Each new object gets its own seat and price; the price is in cents.
  struct ticket ticket = {.price = price};
  snprintf(ticket.seat, sizeof ticket.seat, "%s", seat);
  return ticket;
}

static void ticket_describe(const struct ticket *ticket, char *out,
                            size_t size) {
  snprintf(out, size, "seat %s, %d.%02d", ticket->seat, ticket->price / 100,
           ticket->price % 100);
}

static void ticket_upgrade(struct ticket *ticket, const char *seat, int extra) {
  snprintf(ticket->seat, sizeof ticket->seat, "%s", seat);
  ticket->price += extra;
}

int main(void) {
  char text[32];
  struct ticket a = ticket_new("C7", 900);
  struct ticket b = ticket_new("C8", 900);
  ticket_describe(&a, text, sizeof text);
  printf("a: %s\n", text);
  ticket_describe(&b, text, sizeof text);
  printf("b: %s\n", text);
  puts("upgrade a to A1, 6.50 more");
  ticket_upgrade(&a, "A1", 650);
  ticket_describe(&a, text, sizeof text);
  printf("a: %s\n", text);
  ticket_describe(&b, text, sizeof text);
  printf("b: %s\n", text);
  return 0;
}

In C: C has no methods. ticket_describe(&a, …) plays the method, and its first parameter is the object that the other languages pass for you as self or this.

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 Ticket
{
    // Each new object gets its own seat and price; the price is in cents.
    public function __construct(
        private string $seat,
        private int $price,
    ) {}

    public function describe(): string
    {
        $cents = $this->price % 100;

        return sprintf('seat %s, %d.%02d', $this->seat, intdiv($this->price, 100), $cents);
    }

    public function upgrade(string $seat, int $extra): void
    {
        $this->seat = $seat;
        $this->price += $extra;
    }
}

$a = new Ticket('C7', 900);
$b = new Ticket('C8', 900);
echo "a: {$a->describe()}\n";
echo "b: {$b->describe()}\n";
echo "upgrade a to A1, 6.50 more\n";
$a->upgrade('A1', 650);
echo "a: {$a->describe()}\n";
echo "b: {$b->describe()}\n";

In PHP: Constructor promotion, private string $seat in the parameter list, declares the field and assigns it in one step.

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 Ticket {
    seat: String,
    price: u32,
}

impl Ticket {
    fn new(seat: &str, price: u32) -> Self {
        // Each new object gets its own seat and price; the price is in cents.
        Ticket {
            seat: seat.to_string(),
            price,
        }
    }

    fn describe(&self) -> String {
        format!(
            "seat {}, {}.{:02}",
            self.seat,
            self.price / 100,
            self.price % 100
        )
    }

    fn upgrade(&mut self, seat: &str, extra: u32) {
        self.seat = seat.to_string();
        self.price += extra;
    }
}

fn main() {
    let mut a = Ticket::new("C7", 900);
    let b = Ticket::new("C8", 900);
    println!("a: {}", a.describe());
    println!("b: {}", b.describe());
    println!("upgrade a to A1, 6.50 more");
    a.upgrade("A1", 650);
    println!("a: {}", a.describe());
    println!("b: {}", b.describe());
}

In Rust: &self is the explicit receiver, a borrowed ticket. upgrade takes &mut self, so a is declared mut and b is not. Ticket::new is a naming convention, not a keyword.

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 Ticket {
  seat: string;
  price: number;

  constructor(seat: string, price: number) {
    // Each new object gets its own seat and price; the price is in cents.
    this.seat = seat;
    this.price = price;
  }

  describe(): string {
    const cents = String(this.price % 100).padStart(2, "0");
    return `seat ${this.seat}, ${Math.trunc(this.price / 100)}.${cents}`;
  }

  upgrade(seat: string, extra: number): void {
    this.seat = seat;
    this.price += extra;
  }
}

const a = new Ticket("C7", 900);
const b = new Ticket("C8", 900);
console.log(`a: ${a.describe()}`);
console.log(`b: ${b.describe()}`);
console.log("upgrade a to A1, 6.50 more");
a.upgrade("A1", 650);
console.log(`a: ${a.describe()}`);
console.log(`b: ${b.describe()}`);

In TypeScript: The fields are declared first, and strict mode checks that the constructor gives each one a value. Parameter properties would do both at once, but they are not erasable syntax.

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 Ticket struct {
	seat  string
	price int
}

func NewTicket(seat string, price int) *Ticket {
	// Each new object gets its own seat and price; the price is in cents.
	return &Ticket{seat: seat, price: price}
}

func (t *Ticket) Describe() string {
	return fmt.Sprintf("seat %s, %d.%02d", t.seat, t.price/100, t.price%100)
}

func (t *Ticket) Upgrade(seat string, extra int) {
	t.seat = seat
	t.price += extra
}

func main() {
	a := NewTicket("C7", 900)
	b := NewTicket("C8", 900)
	fmt.Println("a:", a.Describe())
	fmt.Println("b:", b.Describe())
	fmt.Println("upgrade a to A1, 6.50 more")
	a.Upgrade("A1", 650)
	fmt.Println("a:", a.Describe())
	fmt.Println("b:", b.Describe())
}

In Go: The receiver, (t *Ticket), is written before the method name. It is a pointer, so Upgrade changes the caller's ticket. NewTicket is Go's convention for a constructor.

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 Ticket {
  private(set) var seat: String
  private(set) var price: Int

  init(seat: String, price: Int) {
    // Each new object gets its own seat and price; the price is in cents.
    self.seat = seat
    self.price = price
  }

  func describe() -> String {
    let cents = price % 100
    return "seat \(seat), \(price / 100).\(cents < 10 ? "0" : "")\(cents)"
  }

  func upgrade(to seat: String, extra: Int) {
    self.seat = seat
    price += extra
  }
}

let a = Ticket(seat: "C7", price: 900)
let b = Ticket(seat: "C8", price: 900)
print("a: \(a.describe())")
print("b: \(b.describe())")
print("upgrade a to A1, 6.50 more")
a.upgrade(to: "A1", extra: 650)
print("a: \(a.describe())")
print("b: \(b.describe())")

In Swift: init must give every stored property a value before it returns, and the compiler checks it. Argument labels make the call read upgrade(to:extra:).

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 seat and its price, in cents.
class Ticket
  def initialize(seat, price)
    # Each new object gets its own seat and price; the price is in cents.
    @seat = seat
    @price = price
  end

  def describe
    "seat #{@seat}, #{@price / 100}.#{(@price % 100).to_s.rjust(2, '0')}"
  end

  def upgrade(seat, extra)
    @seat = seat
    @price += extra
  end
end

a = Ticket.new('C7', 900)
b = Ticket.new('C8', 900)
puts "a: #{a.describe}"
puts "b: #{b.describe}"
puts 'upgrade a to A1, 6.50 more'
a.upgrade('A1', 650)
puts "a: #{a.describe}"
puts "b: #{b.describe}"

In Ruby: Instance variables such as @seat come into being when initialize assigns them, as Python's attributes do, and every method of the object can read them.

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

// Each new object gets its own seat and price; the price is in cents.
class Ticket(
    seat: String,
    price: Int,
) {
    var seat = seat
        private set
    var price = price
        private set

    fun describe(): String {
        val cents = (price % 100).toString().padStart(2, '0')
        return "seat $seat, ${price / 100}.$cents"
    }

    fun upgrade(
        seat: String,
        extra: Int,
    ) {
        this.seat = seat
        price += extra
    }
}

fun main() {
    val a = Ticket("C7", 900)
    val b = Ticket("C8", 900)
    println("a: ${a.describe()}")
    println("b: ${b.describe()}")
    println("upgrade a to A1, 6.50 more")
    a.upgrade("A1", 650)
    println("a: ${a.describe()}")
    println("b: ${b.describe()}")
}

In Kotlin: The class header is the primary constructor. Each property starts from a parameter of the same name and keeps a private setter, so this.seat in upgrade means the property.

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 Ticket {
  // Each new object gets its own seat and price; the price is in cents.
  Ticket(this._seat, this._price);

  String _seat;
  int _price;

  String describe() =>
      'seat $_seat, ${_price ~/ 100}.${(_price % 100).toString().padLeft(2, '0')}';

  void upgrade(String seat, int extra) {
    _seat = seat;
    _price += extra;
  }
}

void main() {
  final a = Ticket('C7', 900);
  final b = Ticket('C8', 900);
  print('a: ${a.describe()}');
  print('b: ${b.describe()}');
  print('upgrade a to A1, 6.50 more');
  a.upgrade('A1', 650);
  print('a: ${a.describe()}');
  print('b: ${b.describe()}');
}

In Dart: Ticket(this._seat, this._price) uses initializing formals, which store each argument in the field of the same name before the body runs.

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

a: seat C7, 9.00
b: seat C8, 9.00
upgrade a to A1, 6.50 more
a: seat A1, 15.50
b: seat C8, 9.00

All 14 printed the same five lines. The last two are the point. upgrade ran on a, so a became seat A1 at 15.50, while b kept seat C8 at 9.00. Both tickets ran the same describe and the same upgrade, written once in the class, and each run read and changed the field: A named value stored in each object of a class, such as a ticket's seat and price. Also called an attribute, a member variable or a property, depending on the language. of one object only. That is the division of labour in every class-based language: the class holds what all its objects share, the method: A function defined in a class and called on one of its objects, such as ticket.describe(). It receives that object, as self or this, and can read and change its fields., and each object holds what is its own, the values of its fields.

The notes under the tabs show how much the syntax varies around that one idea. Python and Ruby create a field the moment the constructor assigns it. TypeScript, Java, C#, C++, Kotlin, Swift and Dart declare the fields in the class, and PHP declares them in the constructor’s parameter list. C has no methods at all and passes the struct to plain functions, and Go and Rust write their methods outside the type, in a receiver and in an impl block.

Key idea

A class is written once and shared: its methods exist once for all its objects, while each object made from it holds its own values for the fields the class lists.

Checkpoint

Check: The class and its objects

  1. Question 1 of 2 The program ran a.upgrade("A1", 650). What does b.describe() return afterwards?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "seat C8, 9.00", because upgrade changed only the fields of a

    Methods are shared, fields are per object. a.upgrade("A1", 650) set a's seat to A1 and its price to 1550, and the program's last line, "b: seat C8, 9.00", shows that b kept its own values.

  2. Question 2 of 2 In a class-based language, which of these does the class hold once for all its objects?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The code of its methods, such as describe and upgrade

    The class lists the fields and defines the methods. Field values live in each instance; method code lives in the class, which is why upgrading a changed nothing in b.

self and this are the object a method was called on

When describe runs, how does it know which ticket’s seat to read? Every method receives the object it was called on, its receiver: The object a method was called on, passed to the method as self or this. In a.describe(), the receiver is a.. Python makes the receiver a visible first parameter, self, and the call a.describe() hands a to it. Step through one call on each ticket:

Interactive · step by step What a.describe() does Step through one call on each ticket. Watch where the method is found and which object self is.
What a.describe() does, step 6 of 6. Showing ticket a (seat A1, price 1550), ticket b, class Ticket, describe(self) (called with a). In focus: ticket a.its classits classselfticket aseat A1, price 1550ticket bseat C8, price 900class Ticketdescribe(), upgrade()describe(self)called with a

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Two objects. The program made a and b from the same class. Each object holds its own seat and price and remembers its class; neither holds a copy of any method.
  2. Look in a first. a.describe() starts at the object. Its own fields are seat and price, and describe is not one of them, so the lookup goes on to its class.
  3. Found in the class. Ticket holds describe, once for every ticket. Python calls it with a as the first argument, so a.describe() is the same call as Ticket.describe(a).
  4. self is a. Inside the method, self is the object a. self.seat and self.price read a's fields, so the call returns "seat C7, 9.00".
  5. Same method, ticket b. b.describe() runs the very same method with self bound to b. It reads b's fields and returns "seat C8, 9.00". One method, two objects, two answers.
  6. upgrade changes self. a.upgrade("A1", 650) also runs with self bound to a, so it changes a's fields and nothing else. a is now seat A1 at 15.50, and b is still seat C8 at 9.00.

The Python tutorial states the rule exactly: the call x.f() “is exactly equivalent to MyClass.f(x)”. The next program checks it and shows where Python keeps each part. Press Run, then change Ticket.describe(b) to Ticket.describe(a) and run it again:

Where the fields and the method live Python · self/where_methods_live.py
class Ticket:
    def __init__(self, seat: str, price: int) -> None:
        self.seat = seat
        self.price = price

    def describe(self) -> str:
        return f"seat {self.seat}, {self.price // 100}.{self.price % 100:02d}"


a = Ticket("C7", 900)
b = Ticket("C8", 900)

# The object holds its fields; the class holds the method, once for every ticket.
print("a's own fields:", vars(a))
print("describe in a's fields?", "describe" in vars(a))
print("describe in Ticket?", "describe" in vars(Ticket))

# A call through the object is a call through the class with the object as self.
print(a.describe())
print(Ticket.describe(a))
print(Ticket.describe(b))

Output

a's own fields: {'seat': 'C7', 'price': 900}
describe in a's fields? False
describe in Ticket? True
seat C7, 9.00
seat C7, 9.00
seat C8, 9.00

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 where_methods_live.py

vars(a) lists only seat and price, because the method is not stored in the object. It lives in the class, and Ticket.describe(a) gives exactly what a.describe() gave. The name self is a convention rather than a keyword, as the tutorial says: Python passes the object in that first position whatever the parameter is called. Other languages pass the receiver too, and differ only in whether you write it:

Language The receiver Written in the method?
Python self, the first parameter Yes, in every method
Rust &self or &mut self Yes
Go (t *Ticket), before the method name Yes
C an ordinary first parameter, a pointer Yes: the method is a plain function
C++23 this const Ticket& self, the explicit object parameter Optional, new in C++23
Java, C#, C++, JavaScript, TypeScript, Kotlin, Dart, PHP this (PHP: $this) No, it is implicit
Swift, Ruby self No, it is implicit

The C++23 row comes from the proposal called deducing this: a method may now name its object as a first parameter marked this, much as Python’s self is named, while the old implicit this keeps working.

Assigning to the parameter instead of the field

In Python, seat = seat inside __init__ assigns a local name to itself and gives the object nothing; the first read of self.seat later raises AttributeError. Write self.seat = seat. Java has the same slip: in a constructor, seat = seat; assigns the parameter to itself, and the field keeps its default, null.

Checkpoint

Check: self, this and the receiver

  1. Question 1 of 2 In the Python program, what does Ticket.describe(b) return, and why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "seat C8, 9.00", because b is passed as self, so the method reads b's fields

    a.describe() and Ticket.describe(a) are the same call, the tutorial's rule. With b in the first position, self is b and the method returns "seat C8, 9.00", the last line the program printed.

  2. Question 2 of 2 A Python constructor reads def __init__(self, seat, price) and then seat = seat and self.price = price. What happens later at print(t.seat)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It raises AttributeError, because seat = seat assigned a local name and never gave the object a seat

    In Python a field exists once something assigns self.seat. The line seat = seat rebinds the parameter to itself, so the object has a price and no seat, and the read raises AttributeError.

Every language decides what an unset field holds

A constructor’s job is to give every field of a new object a value. What happens when it forgets one depends on the language, and the answer decides whether the slip is caught before the program runs, when the field is first read, or never:

Language A field that the constructor never set
Python Does not exist; reading it raises AttributeError
JavaScript Reads as undefined, and arithmetic on it gives NaN
TypeScript Compile error under strict: no initializer, not definitely assigned
Java Gets its default: 0 for an int, null for a String
C# Gets its default, 0 or null; with nullable checks on, a warning for a string
C++ An int left out holds an indeterminate value; reading it is undefined behaviour
C A local struct without an initializer holds indeterminate values; = {0} zeroes them
PHP A typed property read before it is set throws an Error
Rust Compile error: a struct literal must give every field
Go Gets the zero value: 0, "" or nil
Swift Compile error: an initializer must set every stored property
Ruby Reads as nil
Kotlin Compile error: the property must be initialized
Dart Compile error for a field whose type does not allow null

Three groups appear. Rust, Swift, Kotlin, Dart and TypeScript under strict refuse to compile the class. Python and PHP fail at the first read of the field. Java, C#, Go, JavaScript and Ruby quietly supply a default, and C and C++ can leave whatever the memory held. The quiet group is where the worst bugs live: a ticket whose price was never set costs 0 in Java or Go, and nothing anywhere says so. In those languages the constructor itself must check that every field it sets makes sense, as the card’s methods checked the balance.

Java 25: a program without a class around it

JEP 512 lets a beginner’s file be just void main() { IO.println("Hello"); }. The compiler declares a class around such a compact source file, a class that code cannot name, so its fields and methods still belong to a class.

Checkpoint

Check: Fields the constructor never set

  1. Question 1 of 2 A Go program builds Ticket{seat: "C7"} and never sets price. What does t.price hold?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0, the zero value of an int

    Go's specification gives each element of a new value the zero value for its type: 0, "" or nil. A ticket that costs 0 is valid Go, which is why a constructor function should check what it is given.

  2. Question 2 of 2 A constructor forgets to set a ticket's price. In which of these languages is the mistake caught before the program ever runs?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • Swift
    • Rust
    • TypeScript with strict on

    Rust, Swift, Kotlin, Dart and strict TypeScript refuse to compile a class or struct that leaves a field unset. Python and PHP fail at the first read; Java, C#, Go, JavaScript and Ruby supply a default without a word.

struct and class mean different things in different languages

In C++ the keywords struct and class make the same kind of type. They differ only in the access you get without asking for one: a struct starts its members and its base classes as public, a class starts them as private. Classes in C++ are values, too: Ticket t("C7", 900); creates the object right there in the variable, with no new.

In C# and Swift the difference is much bigger. A struct is a value type, copied whole when it is assigned or passed to a function, while a class is a reference type, so assigning it gives a second name to the same object. Rust, Go and C have only structs: Rust and Go attach the methods from outside, and C passes the struct to functions.

Language struct class
C++ Members public by default Members private by default; otherwise identical
C# A value type: copied on assignment A reference type: shared on assignment
Swift A value type: copied on assignment A reference type: shared on assignment
Rust, Go The type that holds fields; methods in impl blocks or receivers No such keyword
C Fields only; functions take a pointer to it No such keyword

The C# and Swift rows matter as soon as a program says b = a: with a struct, b is a second ticket, and with a class it is a second name for the first one. That difference is the subject of the next lesson.

Checkpoint

Check: struct and class across languages

  1. Question 1 of 2 In C++, what is the difference between a type declared with struct and one declared with class?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Only the default access, public for struct and private for class, for members and base classes

    The C++ standard gives struct members public access by default and class members private access, and the same for base classes. Apart from that default the two keywords declare the same kind of type.

  2. Question 2 of 2 In Swift, Ticket is changed from a class to a struct. After var b = a and b.upgrade(…), what happens to a?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: a is unchanged, because b = a copied the struct, so b is a second ticket

    In Swift and C#, a struct is a value type, copied on assignment and when passed, and a class is a reference type, shared. The next lesson runs the difference in all 14 languages.

Practise: a class for a mobile data plan

The exercise asks for a class of your own: a mobile plan with a price, a number of days and a daily data allowance, a constructor that refuses values that make no sense, two methods that compute from the fields and one that returns a new plan instead of changing the old one. Its sample tests run in your browser, in Python or JavaScript:

Exercise · Easy · Python, JavaScript

Write a class for a mobile data plan

Write the class Plan in plan.py (JavaScript: plan.mjs) for a prepaid mobile plan. A plan has three fields, all whole numbers: price in cents, days of validity and daily_mb, the data allowed each day in megabytes (JavaScript: price, days and dailyMb).

  • Plan(price, days, daily_mb) stores the three fields. If any of them is 0 or less, it raises ValueError (JavaScript: RangeError), so no plan object can exist with a price, a validity or an allowance that makes no sense.
  • cost_per_day() (JavaScript: costPerDay()) returns the price per day in whole cents, rounded down: Plan(29900, 28, 1536).cost_per_day() is 1067.
  • total_mb() (JavaScript: totalMb()) returns the data over the whole plan: 1536 × 28 = 43008 for that plan.
  • extended(extra_days) (JavaScript: extended(extraDays)) returns a new plan, extra_days longer, whose price grows by cost_per_day() for each extra day; the plan it was called on does not change. extra_days must be at least 1.

The sample tests build plans, check every method on two plans, extend one and check that the original kept its fields, and try the values the constructor must refuse.

Python · Starter code · plan.py

class Plan:
    """A prepaid mobile plan: price in cents, days of validity and data per day in MB."""

    # Write __init__(self, price, days, daily_mb), cost_per_day(), total_mb() and extended(extra_days).
The sample tests · test_plan.py
from plan import Plan


def refused(make):
    """Whether calling make raises ValueError."""
    try:
        make()
    except ValueError:
        return True
    return False


def test_fields_and_methods():
    """the constructor stores the fields; cost_per_day rounds down; total_mb multiplies"""
    plan = Plan(29900, 28, 1536)
    assert (plan.price, plan.days, plan.daily_mb) == (29900, 28, 1536)
    assert plan.cost_per_day() == 1067, f"29900 // 28 is 1067, not {plan.cost_per_day()}"
    assert plan.total_mb() == 43008, f"1536 MB a day for 28 days is 43008, not {plan.total_mb()}"


def test_each_plan_has_its_own_fields():
    """two plans made from the class answer from their own fields"""
    small, large = Plan(15500, 30, 1024), Plan(71900, 84, 2048)
    assert (small.cost_per_day(), large.cost_per_day()) == (516, 855)
    assert (small.total_mb(), large.total_mb()) == (30720, 172032)


def test_extended_returns_a_new_plan():
    """extended(7) is a new plan 7 days longer; the original keeps its fields"""
    plan = Plan(29900, 28, 1536)
    longer = plan.extended(7)
    assert longer is not plan, "extended must return a new Plan, not change this one"
    assert (longer.price, longer.days, longer.daily_mb) == (37369, 35, 1536), "price 29900 + 7 * 1067, 35 days"
    assert (plan.price, plan.days, plan.daily_mb) == (29900, 28, 1536), "the original plan changed"


def test_values_that_make_no_sense_are_refused():
    """a price, days or allowance of 0 or less, and extending by 0 days, raise ValueError"""
    for args in [(0, 28, 1536), (29900, 0, 1536), (29900, 28, 0), (-100, 28, 1536)]:
        assert refused(lambda: Plan(*args)), f"Plan{args} must raise ValueError"
    assert refused(lambda: Plan(29900, 28, 1536).extended(0)), "extended(0) must raise ValueError"

JavaScript · Starter code · plan.mjs

/** A prepaid mobile plan: price in cents, days of validity and data per day in MB. */
export class Plan {
  // Write constructor(price, days, dailyMb), costPerDay(), totalMb() and extended(extraDays).
}
The sample tests · plan.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { Plan } from './plan.mjs';

test('the constructor stores the fields; costPerDay rounds down; totalMb multiplies', () => {
  const plan = new Plan(29900, 28, 1536);
  assert.deepEqual([plan.price, plan.days, plan.dailyMb], [29900, 28, 1536]);
  assert.equal(plan.costPerDay(), 1067, '29900 / 28 rounded down is 1067');
  assert.equal(plan.totalMb(), 43008, '1536 MB a day for 28 days is 43008');
});

test('two plans made from the class answer from their own fields', () => {
  const small = new Plan(15500, 30, 1024);
  const large = new Plan(71900, 84, 2048);
  assert.deepEqual([small.costPerDay(), large.costPerDay()], [516, 855]);
  assert.deepEqual([small.totalMb(), large.totalMb()], [30720, 172032]);
});

test('extended(7) is a new plan 7 days longer; the original keeps its fields', () => {
  const plan = new Plan(29900, 28, 1536);
  const longer = plan.extended(7);
  assert.ok(longer !== plan, 'extended must return a new Plan, not change this one');
  assert.deepEqual([longer.price, longer.days, longer.dailyMb], [37369, 35, 1536], 'price 29900 + 7 * 1067, 35 days');
  assert.deepEqual([plan.price, plan.days, plan.dailyMb], [29900, 28, 1536], 'the original plan changed');
});

test('a price, days or allowance of 0 or less, and extending by 0 days, throw a RangeError', () => {
  for (const args of [[0, 28, 1536], [29900, 0, 1536], [29900, 28, 0], [-100, 28, 1536]]) {
    assert.throws(() => new Plan(...args), RangeError, `new Plan(${args}) must throw a RangeError`);
  }
  assert.throws(() => new Plan(29900, 28, 1536).extended(0), RangeError, 'extended(0) must throw a RangeError');
});
A hint

Check the three arguments at the top of the constructor and raise before storing anything. In extended, build the answer with the class itself, Plan(…) (JavaScript: new Plan(…)), from the fields of self (this), instead of assigning to them: a method that returns a new object leaves its own object alone.

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.

JavaScript & TypeScript Online Compiler Try the Ticket class in JavaScript, then add a field and a method of your own.

Now check yourself on the whole lesson. The questions follow your answers: a weak idea comes back first, and a right answer brings a harder question:

Practice that adapts to you

Practise this lesson

8 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 What is an instance?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: An object made from a class, with its own values for the class's fields

    Each call of the constructor makes an instance. Instances share the class's methods and hold their own field values.

  2. Question 2 of 8 A program makes three Ticket objects, a, b and c, and then calls a.upgrade("A1", 650) and c.upgrade("B2", 100). How many tickets still have their original seat?

    Type a number.

    A hint

    Each upgrade changes the fields of the object it is called on, and no other.

    Show the answer: Question 2 of 8

    Answer: 1 tickets

    upgrade changed a and c, one each. b was never the object of a call, so it is the only ticket with its original seat.

  3. Question 3 of 8 In which of these languages does the programmer write the receiver of a method as a parameter?

    Choose every answer that is right.

    Show the answer: Question 3 of 8

    Answer:

    • Go, with a receiver such as (t *Ticket)
    • Rust, with &self or &mut self
    • Python, with self

    Python, Rust and Go write the receiver out, and C passes the struct as an ordinary argument. Java, C#, JavaScript, TypeScript, Kotlin, Dart, PHP, Swift and Ruby provide this or self implicitly; C++23 lets a method name it with an explicit object parameter.

  4. Question 4 of 8 In the Python program, why did "describe" in vars(a) print False?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: vars(a) lists only a's own fields, and describe lives in the class

    An object's own attributes are its fields. The lookup a.describe falls back to the class, which is why vars(Ticket) contains describe and vars(a) does not.

  5. Question 5 of 8 A Java class has a field int price that no constructor sets. What does a new object's price hold?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: 0

    JLS 4.12.5 gives instance variables default values: 0 for numbers, false for booleans and null for references.

  6. Question 6 of 8 Why is a silent default, such as Java's 0 or Go's zero value, more dangerous for a ticket's price than Python's AttributeError?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: The ticket looks valid and costs nothing, so the bug spreads instead of stopping at the first read

    Errors that stop the program at the first read are cheap to find. A default that is also a legal value hides the bug, which is why constructors in those languages should check their arguments.

  7. Question 7 of 8 Which of these languages have structs but no class keyword?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: Rust, Go and C

    Rust writes methods in impl blocks and Go in functions with receivers, both outside the struct. C has no methods at all.

  8. Question 8 of 8 In C++, Ticket is a class. After Ticket b = a; b.upgrade("A1", 650), what happens to a?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: a is unchanged, because C++ objects are values and Ticket b = a copied it

    C++ treats every class as a value type: Ticket b = a; makes a second ticket. To share one ticket under two names, C++ uses a reference, Ticket& b = a;, as the next lesson shows.

Interview questions

Fresher · What is the difference between a class and an object?

A class is a definition and an object is a thing made from it. The class lists the fields every object of its kind will hold and defines the methods they all share; it is written once. An object, an instance of the class, is created at run time by the constructor and holds its own values for those fields. In the lesson’s program, Ticket is the class, and a and b are two objects: both run the same describe and upgrade code, stored once in the class, yet upgrading a to seat A1 left b at seat C8 and 9.00. A useful test in an answer: methods belong to the class, field values belong to each object, and a method finds its object through self or this.

Interviewers rarely stop there. Here are three follow-ups to try, with four answers each; the feedback explains what each answer covers:

Checkpoint

Interview round: three follow-up questions

  1. Question 1 of 3 An interviewer asks: a program makes a million Ticket objects. Are there a million copies of describe in memory?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: No. The method's code exists once, in the class; each object holds its fields and a way to find its class

    Each object stores its own field values plus a link to its class: Python's type(a), Java's class pointer, the virtual table pointer of a C++ object that has virtual methods. The method code is shared by all of them.

  2. Question 2 of 3 "Must the first parameter of a Python method be called self?" Which answer is right?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: No, self is a convention; Python passes the object as the first argument whatever the name, but every method needs that parameter

    Python's tutorial says the name self has absolutely no special meaning to Python. What matters is the position: a.describe() becomes Ticket.describe(a), so the first parameter receives the object.

  3. Question 3 of 3 In C#, should a Ticket whose upgrade method changes its seat and price be a struct or a class?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: A class. It changes over time and code expects one ticket, shared; a mutable struct is copied on every assignment, so changes made to a copy are lost

    C# structs are value types: assignment and argument passing copy them. They suit small values that never change, such as a point or a price. A ticket that is upgraded in place should be a class, so every name sees the change.

Key takeaways

  • A class lists the fields of its objects and defines their methods once; each object made by its constructor holds its own field values, so upgrading ticket a left ticket b unchanged.
  • A method receives the object it was called on: Python and Rust name it in the parameter list, Go and C pass it explicitly, and Java, C#, JavaScript and most others pass it as an implicit this or self.
  • In Python, a.describe() is the call Ticket.describe(a), and vars(a) shows only the fields, because the method lives in the class.
  • A field the constructor forgets is a compile error in Rust, Swift, Kotlin, Dart and strict TypeScript, an error on first read in Python and PHP, and a silent default or worse in Java, C#, Go, JavaScript, Ruby, C and C++.
  • struct and class differ only in default access in C++, but in C# and Swift a struct is copied on assignment while a class is shared.

Terms in this lesson

class
A definition of a kind of object: the fields every object of that kind holds and the methods they all share. Ticket is a class; each ticket made from it is an instance.
constructor
The code that runs when a new object is made and gives its fields their first values: __init__ in Python, constructor in JavaScript, a method named like the class in Java and C++, init in Swift.
field
A named value stored in each object of a class, such as a ticket's seat and price. Also called an attribute, a member variable or a property, depending on the language.
instance
One object made from a class by its constructor. Instances share the class's methods and each holds its own values for the class's fields.
method
A function defined in a class and called on one of its objects, such as ticket.describe(). It receives that object, as self or this, and can read and change its fields.
receiver
The object a method was called on, passed to the method as self or this. In a.describe(), the receiver is a.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.