Design Patterns (GoF and modern) Module 1 – Why patterns, and when not to use them
How language features reshape patterns
Why 16 of the 23 classic patterns get simpler with the right language features, and how Java, Python, TypeScript, Go, Rust and C++ express each one.
What you will learn
- Explain Norvig's finding that 16 of the 23 classic patterns become simpler or invisible in dynamic languages
- Compare how Java, Python, TypeScript, Go, Rust and C++ express interfaces, polymorphism and composition
- Choose between a class-based pattern and a function, closure, sum type or module equivalent
- Read this track's multi-language examples and the toolchain versions they ran with
Before you start
What you’ll learn · Before you start
What you will learn
- Explain Norvig's finding that 16 of the 23 classic patterns become simpler or invisible in dynamic languages
- Compare how Java, Python, TypeScript, Go, Rust and C++ express interfaces, polymorphism and composition
- Choose between a class-based pattern and a function, closure, sum type or module equivalent
- Read this track's multi-language examples and the toolchain versions they ran with
Before you start
On this page
A design pattern describes a shape of code, and how much code that shape needs depends on the language. A Strategy in classic Java is an interface and one class per algorithm; in Python, JavaScript or Go it is often just a function passed around; in Rust it is a generic parameter or a boxed closure. Peter Norvig’s study of the Gang of Four book, first presented in 1996, found that 16 of its 23 patterns have a simpler implementation in Lisp or Dylan than in C++, for at least some uses of each. The problem a pattern solves does not go away. What changes is how much structure you write to solve it, and sometimes the pattern disappears into a language feature you use without naming it.
This lesson shows one Strategy in four languages against a single table of test cases, maps the features of the six languages of this track that reshape patterns, and records the TypeScript syntax that Node.js refuses to run.
Patterns are written in a language
The Gang of Four wrote their sample code in C++ and Smalltalk and described every pattern in terms of classes, objects and inheritance. The C++ of the time had no closures and no way to treat a class as a value that a program can store and pass, and many of the patterns work around exactly that. Norvig grouped the 16 patterns that get simpler by the language feature that simplifies them:
| Feature | How many | Patterns |
|---|---|---|
| First-class types | 6 | Abstract Factory, Flyweight, Factory Method, State, Proxy, Chain of Responsibility |
| First-class functions | 4 | Command, Strategy, Template Method, Visitor |
| Macros | 2 | Interpreter, Iterator |
| Method combination | 2 | Mediator, Observer |
| Multimethods | 1 | Builder |
| Modules | 1 | Facade |
Two of those features matter most for the languages of this track. First-class functions, which every one of them now has in some form, shrink the patterns whose objects exist only to carry one piece of behaviour: Command, Strategy, Template Method and Visitor. First-class types, classes that are values you can store and pass, make many creational patterns a dictionary of classes, which Python and JavaScript do directly. The study’s own wording is careful: “for at least some uses of each pattern”. A Strategy with several operations and its own state still deserves a class.
One Strategy, four languages, one test table
A parking lot charges by one of three rules: 20 for every started hour, a flat 100, or the first hour free and then 30 for every started hour. The rule is chosen by name, and the lot must not change when a rule is added. That is Strategy, and the shape of its code depends on what the language gives you:
One Strategy, two shapes: classes behind an interface, or functions in a map
Text description of the diagram
Two panels, one above the other.
The first panel, "Class form: 1 interface + 3 classes", shows a ParkingLot box with an arrow labelled "holds one" to an interface box, PricingRule, whose one operation is fee(minutes). Three classes, Hourly, Flat and FirstHourFree, each join the interface with a dashed line and a hollow triangle: each one realises it.
The second panel, "Function form: 3 functions in a map", shows a ParkingLot box with an arrow labelled "looks up one" to a map named RULES, holding hourly, flat and first-hour-free. A dashed arrow from the map leads to a note: every value is a function from minutes to a fee. The signature is the whole interface, and no class is needed.
In Python each rule is a plain function, and a dictionary picks one. The program reads the shared table of cases,
cases.txt, and checks every fee against it:
Python · strategy/strategy.py
"""Strategy in Python: each pricing rule is a plain function, and a dict picks one by name."""
import math
def hourly(minutes):
return 20 * math.ceil(minutes / 60)
def flat(minutes):
return 100
def first_hour_free(minutes):
return 0 if minutes <= 60 else 30 * math.ceil((minutes - 60) / 60)
RULES = {"hourly": hourly, "flat": flat, "first-hour-free": first_hour_free}
cases = [line.split() for line in open("cases.txt") if line.strip() and not line.startswith("#")]
fees = [RULES[rule](int(minutes)) for rule, minutes, _ in cases]
passed = sum(fee == int(expected) for fee, (_, _, expected) in zip(fees, cases))
print("fees:", *fees)
print(f"{passed} of {len(cases)} cases pass") Output
fees: 20 20 40 60 100 100 0 30 90 9 of 9 cases pass
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 strategy.py
strategy/cases.txt
# rule minutes expected-fee: the shared test table that every version of the parking-fee strategies must pass
hourly 1 20
hourly 60 20
hourly 61 40
hourly 150 60
flat 5 100
flat 600 100
first-hour-free 60 0
first-hour-free 61 30
first-hour-free 185 90 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
The same rules in TypeScript, Java and C++, built and run by one session that prints each compiler’s version:
strategy/session.mjs
// Builds and runs the TypeScript, Java and C++ versions as you would in a terminal, and prints the session.
// TypeScript runs on this Node.js, which strips the types. Java is Homebrew's OpenJDK, built with --release 25 so
// that the code may use nothing newer than Java 25; C++ is the system clang++. Version lines are printed first,
// without release dates. A command that fails stops the session with exit status 1.
import { spawnSync } from 'node:child_process';
const PATH = ['/opt/homebrew/opt/openjdk/bin', '/usr/bin', '/bin'].join(':');
const run = (command) => spawnSync('/bin/sh', ['-c', command], { encoding: 'utf8', env: { ...process.env, PATH } });
const undated = (text) => text.split('\n')[0].replace(/\s+\d{4}-\d{2}-\d{2}/, '');
console.log(`# Node.js ${process.version}`);
console.log(`# ${undated(run('java -version 2>&1').stdout)}`);
console.log(`# ${undated(run('clang++ --version').stdout)}`);
for (const command of [
`${process.execPath} strategy.ts`,
'javac --release 25 -d classes Strategy.java',
'java -cp classes Strategy',
'clang++ -std=c++23 -Wall -Wextra -O1 strategy.cpp -o strategy',
'./strategy',
]) {
console.log(`$ ${command.replace(process.execPath, 'node')}`);
const result = run(`${command} 2>&1`);
process.stdout.write(result.stdout);
if (result.status !== 0) {
console.log(`[exit status ${result.status}]`);
process.exit(1);
}
} Output
# Node.js v24.21.0 # openjdk version "27" # Apple clang version 21.0.0 (clang-2100.3.34.2) $ node strategy.ts fees: 20 20 40 60 100 100 0 30 90 9 of 9 cases pass $ javac --release 25 -d classes Strategy.java $ java -cp classes Strategy fees: 20 20 40 60 100 100 0 30 90 9 of 9 cases pass (classes) fees: 20 20 40 60 100 100 0 30 90 9 of 9 cases pass (lambdas) $ clang++ -std=c++23 -Wall -Wextra -O1 strategy.cpp -o strategy $ ./strategy fees: 20 20 40 60 100 100 0 30 90 9 of 9 cases pass
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node session.mjs
TypeScript · strategy/strategy.ts
// Strategy in TypeScript: a function type is the interface, and an object maps names to strategies.
import { readFileSync } from 'node:fs';
type PricingRule = (minutes: number) => number;
const startedHours = (minutes: number): number => Math.ceil(minutes / 60);
const rules: Record<string, PricingRule> = {
hourly: (minutes) => 20 * startedHours(minutes),
flat: () => 100,
'first-hour-free': (minutes) => (minutes <= 60 ? 0 : 30 * startedHours(minutes - 60)),
};
const cases = readFileSync('cases.txt', 'utf8')
.split('\n')
.filter((line) => line.trim() !== '' && !line.startsWith('#'))
.map((line) => line.split(' '));
const fees = cases.map(([rule, minutes]) => rules[rule](Number(minutes)));
const passed = fees.filter((fee, i) => fee === Number(cases[i][2])).length;
console.log('fees:', ...fees);
console.log(`${passed} of ${cases.length} cases pass`); Java · strategy/Strategy.java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
/** Strategy in Java: the classic form, an interface and one class per rule, then the same rules as lambdas. */
public class Strategy {
interface PricingRule {
int fee(int minutes);
}
static int startedHours(int minutes) {
return (minutes + 59) / 60;
}
static final class Hourly implements PricingRule {
public int fee(int minutes) {
return 20 * startedHours(minutes);
}
}
static final class Flat implements PricingRule {
public int fee(int minutes) {
return 100;
}
}
static final class FirstHourFree implements PricingRule {
public int fee(int minutes) {
return minutes <= 60 ? 0 : 30 * startedHours(minutes - 60);
}
}
// A one-method interface is a functional interface, so each rule can also be a lambda.
static final Map<String, PricingRule> CLASSES =
Map.of("hourly", new Hourly(), "flat", new Flat(), "first-hour-free", new FirstHourFree());
static final Map<String, PricingRule> LAMBDAS = Map.of(
"hourly", minutes -> 20 * startedHours(minutes),
"flat", minutes -> 100,
"first-hour-free", minutes -> minutes <= 60 ? 0 : 30 * startedHours(minutes - 60));
public static void main(String[] args) throws Exception {
List<String[]> cases = Files.readAllLines(Path.of("cases.txt")).stream()
.filter(line -> !line.isBlank() && !line.startsWith("#"))
.map(line -> line.split(" "))
.toList();
for (Map<String, PricingRule> rules : List.of(CLASSES, LAMBDAS)) {
StringBuilder fees = new StringBuilder("fees:");
int passed = 0;
for (String[] c : cases) {
int fee = rules.get(c[0]).fee(Integer.parseInt(c[1]));
fees.append(' ').append(fee);
if (fee == Integer.parseInt(c[2])) passed++;
}
System.out.println(fees);
System.out.println(passed + " of " + cases.size() + " cases pass (" + (rules == CLASSES ? "classes" : "lambdas") + ")");
}
}
} C++ · strategy/strategy.cpp
// Strategy in C++23: std::function holds any callable with the right signature, so each rule is a lambda.
#include <fstream>
#include <functional>
#include <map>
#include <print>
#include <sstream>
#include <string>
#include <vector>
using PricingRule = std::function<int(int)>;
int started_hours(int minutes) { return (minutes + 59) / 60; }
int main() {
const std::map<std::string, PricingRule> rules{
{"hourly", [](int minutes) { return 20 * started_hours(minutes); }},
{"flat", [](int) { return 100; }},
{"first-hour-free", [](int minutes) { return minutes <= 60 ? 0 : 30 * started_hours(minutes - 60); }},
};
std::ifstream file{"cases.txt"};
std::string line;
std::vector<int> fees;
int cases = 0, passed = 0;
while (std::getline(file, line)) {
if (line.empty() || line.starts_with('#')) continue;
std::istringstream fields{line};
std::string rule;
int minutes = 0, expected = 0;
fields >> rule >> minutes >> expected;
const int fee = rules.at(rule)(minutes);
fees.push_back(fee);
++cases;
if (fee == expected) ++passed;
}
std::print("fees:");
for (int fee : fees) std::print(" {}", fee);
std::println("\n{} of {} cases pass", passed, cases);
} All four print the same nine fees and pass all nine cases. What differs is the ceremony around each rule:
- Java shows the classic form first: an interface,
PricingRule, and one class per rule. Because the interface has exactly one abstract method, it is a functional interface, so the second map holds the same rules as lambdas. The session compiles withjavac --release 25, which accepts only the Java 25 language and API. - TypeScript needs no classes at all. The interface is a function type,
(minutes: number) => number, and because TypeScript’s types are structural, any function of that shape fits. - C++ stores each lambda in a
std::function<int(int)>, which can hold any callable with that signature. A template parameter would avoid the indirection, at the price of one compiled copy of the lot per rule type. - Python uses functions and a dictionary, as above; a
typing.Protocolcould name the signature for a type checker.
Go and Rust, the other two languages of this track, need no classes either. In Go a rule is a value of a function
type such as func(minutes int) int, often a closure that captures its rate. In Rust it is either a generic parameter
bounded by Fn(u32) -> u32, compiled into one specialised copy per closure type, or a Box<dyn Fn(u32) -> u32>, a
trait object that is called through a table of function pointers at run time. A HashMap of rules needs the second
form, because every closure has its own type and a generic parameter stands for one type at a time.
Classes are still the better shape when a strategy has more than one operation, a fee and a description say, when it keeps state between calls, or when it must be configured, saved or listed by name. Then an interface with classes, or a small record holding several functions, says more than a bare function.
How the six languages express the building blocks
The same four questions decide how a pattern looks in each language: how an interface is declared, how one is chosen while the program runs, how code is reused without inheritance, and whether the language can describe a closed set of cases and check that every case is handled.
| Criterion | Interfaces | Choosing behaviour at run time |
|---|---|---|
| Java 25 | interface, declared with implements | virtual methods; lambdas for one-method interfaces |
| Python 3.14 | duck typing; abc.ABC or typing.Protocol | any object with the method; functions |
| TypeScript | structural: any value of the right shape | functions and objects as values |
| Go 1.27 | implicit: having the methods is enough | interfaces; functions and closures |
| Rust 1.99 | traits, implemented explicitly | dyn Trait; generics choose at compile time |
| C++23 | abstract classes; concepts for templates | virtual functions, std::function |
| Criterion | Reuse without inheritance | Closed sets of cases |
|---|---|---|
| Java 25 | composition; default methods | sealed types, records, pattern-matching switch |
| Python 3.14 | composition, modules, functions | match statements, with no exhaustiveness check |
| TypeScript | composition, modules | discriminated unions, checked by narrowing |
| Go 1.27 | struct embedding | no sum types: an interface and a type switch |
| Rust 1.99 | traits with default methods | enums with exhaustive match |
| C++23 | composition, templates | std::variant with std::visit |
A few entries change patterns more than the others:
- Sum types against Visitor. Visitor adds operations to a fixed hierarchy through double dispatch. A language
with sum types writes each operation as one function with an exhaustive match instead: a Rust enum, a Java sealed
interface with a pattern-matching
switch, a TypeScript union with a shared tag. Adding a case then makes the compiler point at every match to update, the same trade-off as Visitor with much less code. - Go has no inheritance. Effective Go puts it plainly: Go has no type-driven subclassing, and embedding lets a struct borrow another type’s methods. Patterns built on subclassing, such as Template Method, become a struct that holds a function or a small interface for the steps that vary.
- Go 1.27 added generic methods, but interface methods may still not declare type parameters, and a generic method
cannot implement an interface method. A Visitor interface with a generic
Acceptthat returns any type is therefore still impossible in Go. - Rust has no inheritance either, and its ownership rules shape patterns that share objects: an Observer that keeps subscribers alive must decide who owns them.
- C++23 on Apple clang 21. The C++23 standard has
std::generator, which makes an Iterator a coroutine, andstd::move_only_function, but the libc++ that ships with Apple clang 21 implements neither yet, and both fail to compile there. This track’s C++ examples avoid them.
TypeScript on Node.js: only erasable syntax
Node.js 24 runs .ts files directly by replacing every type annotation with whitespace, without checking the types;
type stripping is stable since Node.js 24.12.0. Syntax that needs generated JavaScript, such as enums, namespaces with
runtime code and parameter properties, cannot be stripped, so Node.js refuses it. Here is a pricing rule written with
a parameter property, and the same class with only erasable syntax:
typescript/run_ts.mjs
// Runs both TypeScript files on this Node.js, which executes .ts files by stripping their types, and prints what
// each run shows: the output, or the first line of the error and the exit status.
import { spawnSync } from 'node:child_process';
console.log(`# Node.js ${process.version}`);
for (const file of ['param_property.ts', 'erasable.ts']) {
console.log(`$ node ${file}`);
const result = spawnSync(process.execPath, [file], { encoding: 'utf8' });
if (result.status === 0) {
process.stdout.write(result.stdout);
} else {
const error = result.stderr.split('\n').find((line) => line.includes('ERR_'));
console.log(error ?? result.stderr.trim());
console.log(`[exit status ${result.status}]`);
}
} Output
# Node.js v24.21.0 $ node param_property.ts SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript parameter property is not supported in strip-only mode [exit status 1] $ node erasable.ts 40
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node run_ts.mjs
typescript/param_property.ts
// A pricing rule as a class with a parameter property: TypeScript turns `private readonly rate` into a field and an
// assignment, code that Node.js would have to generate, so a run that only strips types refuses it.
class HourlyRate {
constructor(private readonly rate: number) {}
fee(minutes: number): number {
return this.rate * Math.ceil(minutes / 60);
}
}
console.log(new HourlyRate(20).fee(61)); typescript/erasable.ts
// The same class with only erasable syntax: delete the types and valid JavaScript is left.
class HourlyRate {
readonly #rate: number;
constructor(rate: number) {
this.#rate = rate;
}
fee(minutes: number): number {
return this.#rate * Math.ceil(minutes / 60);
}
}
console.log(new HourlyRate(20).fee(61)); TypeScript 5.8 added the --erasableSyntaxOnly option, which reports such syntax when the compiler checks your code,
before anything runs. This track’s TypeScript uses only erasable syntax: fields assigned in the constructor instead of
parameter properties, and unions of string literals or plain objects instead of enums.
Reading the examples in this track
Most pattern lessons show one program in several languages, as tabs of one example. Python and JavaScript examples run in your browser with the Run button, and every example shows the output it printed, with the command and the toolchain version under it. Programs in compiled languages come with a session file that builds and runs them the way you would in a terminal; its output starts with each compiler’s version line and shows every command it typed. When two languages need different code for the same idea, the lesson says why.
Practise: a Java-style Strategy, ported properly
shipping.py was ported from Java line by line: an abstract class and three one-method subclasses. Rewrite it with
one function per rule and a dictionary. The sample tests check costs the starter was written for and weights it never
saw, call the rules directly, add a rule from outside, and confirm that no class is left.
Exercise · Easy · Python
Port a Java-style Strategy to idiomatic Python
shipping.py was ported from Java line by line: an abstract ShippingRule class, one subclass per rule, each with a single cost method, and a RULES dict of instances. Python has first-class functions, so the three classes carry no information that three functions would not. Rewrite the module the Python way:
1. One plain function per rule, each taking the weight in grams and returning the cost: standard is 40 plus 10 for every started kilogram, express is 90 plus 15 for every started kilogram, and pickup is always 0. 2. RULES maps the names "standard", "express" and "pickup" to those functions. 3. shipping_cost(method, grams) looks the method up in RULES when it is called and returns the cost; an unknown method raises KeyError.
The sample tests check costs for weights the starter was written for and for weights it never saw, call the functions in RULES directly, add a new rule to RULES from the test file, and read your source: no class may be left in the module.
Starter code · shipping.py
"""Shipping costs, ported from Java line by line: an abstract rule and one subclass per rule."""
import math
from abc import ABC, abstractmethod
class ShippingRule(ABC):
@abstractmethod
def cost(self, grams):
"""The cost of shipping a parcel of this many grams."""
class Standard(ShippingRule):
def cost(self, grams):
return 40 + 10 * math.ceil(grams / 1000)
class Express(ShippingRule):
def cost(self, grams):
return 90 + 15 * math.ceil(grams / 1000)
class Pickup(ShippingRule):
def cost(self, grams):
return 0
RULES = {"standard": Standard(), "express": Express(), "pickup": Pickup()}
def shipping_cost(method, grams):
return RULES[method].cost(grams) The sample tests · test_shipping.py
import ast
import inspect
import shipping
from shipping import RULES, shipping_cost
def test_costs_the_starter_was_written_for():
"""the same costs as the Java-style port"""
assert shipping_cost("standard", 500) == 50
assert shipping_cost("standard", 1000) == 50
assert shipping_cost("standard", 1001) == 60
assert shipping_cost("express", 2500) == 135
assert shipping_cost("pickup", 9000) == 0
def test_weights_it_never_saw():
"""started kilograms for any weight"""
assert shipping_cost("standard", 12345) == 170
assert shipping_cost("express", 1) == 105
assert shipping_cost("express", 7000) == 195
assert shipping_cost("pickup", 1) == 0
def test_rules_are_plain_functions():
"""RULES maps each name to a function that takes grams"""
assert set(RULES) == {"standard", "express", "pickup"}
assert all(inspect.isfunction(rule) for rule in RULES.values()), "store the functions themselves in RULES"
assert RULES["express"](1000) == 105
assert RULES["standard"](2000) == 60
def test_a_new_rule_joins_from_outside():
"""shipping_cost looks the rule up when it is called"""
RULES["drone"] = lambda grams: 200
try:
assert shipping_cost("drone", 100) == 200
finally:
del RULES["drone"]
def test_unknown_method():
"""an unknown method raises KeyError"""
try:
shipping_cost("teleport", 100)
except KeyError:
return
raise AssertionError("shipping_cost('teleport', 100) should raise KeyError")
def test_no_class_hierarchy_is_left():
"""first-class functions replace the one-method classes"""
tree = ast.parse(inspect.getsource(shipping))
classes = [node.name for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
assert classes == [], f"classes left in shipping.py: {classes}" A hint
Each subclass's cost method is already the body of a function: move it out of its class, give the function the rule's name and drop self. "Every started kilogram" is math.ceil(grams / 1000), which counts 1001 grams as two kilograms. Then RULES holds the functions themselves, without calling them, and shipping_cost becomes one line that calls RULES[method] with the weight.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 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.
Check yourself
8 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · Why do some design patterns disappear in languages with first-class functions?
Because those patterns exist to pass one piece of behaviour around, and in languages without functions as values the only way to do that is an object of a class with one method. Strategy, Command, Template Method and Visitor all have that shape: an interface with one operation and a class per variant. When functions are values, the variant is just a function, a lambda or a closure, stored in a variable or a dictionary and called later, so the interface and the classes vanish. The pattern’s idea stays, a family of interchangeable behaviours chosen at run time, but the code shrinks to a few lines. Peter Norvig found 16 of the 23 classic patterns simpler in dynamic languages for this kind of reason. Classes come back when the behaviour needs several operations or its own state.
Key takeaways
- A pattern’s problem is the same in every language; the amount of code it needs depends on the language’s features.
- Norvig found 16 of the 23 classic patterns simpler or invisible in Lisp or Dylan; first-class functions alone simplify Command, Strategy, Template Method and Visitor.
- Strategy is an interface and classes in classic Java, a function type in TypeScript,
std::functionin C++, a function in Python and Go, and a generic bound or a boxed closure in Rust. - Sum types with exhaustive matching replace much of Visitor; Go and Rust have no inheritance, so subclass-based patterns become functions or small interfaces.
- Node.js runs TypeScript by stripping types, so parameter properties and enums fail;
--erasableSyntaxOnlycatches them first.
References
- Design Patterns in Dynamic Languages (slides 9 and 10) (Peter Norvig)
- Effective Go: Interfaces and other types, Embedding (The Go Authors)
- Go 1.27 Release Notes (generic methods) (The Go Authors)
- The Go Programming Language Specification: Type switches (The Go Authors)
- The Rust Programming Language: Using Trait Objects to Abstract over Shared Behavior (The Rust Project)
- The Rust Programming Language: The match Control Flow Construct (The Rust Project)
- Modules: TypeScript (Node.js v24 documentation) (OpenJS Foundation)
- TypeScript 5.8 release notes: the --erasableSyntaxOnly option (Microsoft (TypeScript))
- TypeScript Handbook: Type Compatibility (Microsoft (TypeScript))
- The Java Language Specification, Java SE 25: Functional Interfaces (9.8) (Oracle)
- JEP 441: Pattern Matching for switch (OpenJDK)
- JEP 409: Sealed Classes (OpenJDK)
- typing.Protocol (Python Software Foundation)
- PEP 634: Structural Pattern Matching (Python Software Foundation)
- libc++ C++23 status (LLVM Project)
- Working Draft, Programming Languages: C++: class template function and variant visitation (ISO/IEC JTC1/SC22/WG21 (working draft))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress