1 Definition and origin

1.1 Terminology

The term "first-class object" (also called "first-class citizen") was coined by British computer scientist Christopher Strachey in the 1960s. It describes an entity that supports all operations generally available to other entities in a programming language. These operations typically include being passed as an argument, returned from a function, assigned to a variable, stored in data structures, and tested for equality. The concept is relative: what is considered first-class depends on the language’s design and the set of permissible operations. Strachey originally applied it to functions in the context of lambda calculus and the language CPL.

1.2 Historical development

Strachey introduced the notion in his 1965 lecture notes "Fundamental Concepts in Programming Languages," where he distinguished between first-class and second-class citizens. The idea gained prominence with the development of functional programming languages such as Lisp (late 1950s–1960s), which treated functions as first-class objects from the outset. Later languages like Scheme (1975), ML (1973), and Haskell (1990) further embedded the concept. In the 1990s and 2000s, mainstream languages such as JavaScript and Python adopted first-class functions, making higher-order programming accessible to a broader audience. The term has since been extended to other entities, including classes, continuations, and types.

2 Characteristics

A first-class object in a programming language must support at least the following five fundamental operations.

2.1 Pass as argument

A first-class object can be supplied as an actual parameter to a function or procedure. For example, in JavaScript, a function can be passed to another function:

function greet(f) { f(); }
greet(() => console.log("Hello"));

2.2 Return from function

A first-class object can be the return value of a function. This enables the creation of functions that generate other functions, a key feature for factories and combinators.

def make_adder(n):
    return lambda x: x + n

2.3 Assign to variable

A first-class object can be bound to a name (variable) and referenced later. This is the most basic operation; in many languages, numbers and strings are first-class for this reason.

(setq my-func (lambda (x) (* x 2)))

2.4 Store in data structures

A first-class object can be placed into arrays, lists, records, or other compound data structures. For instance, an array of functions can be created and indexed.

const ops = [add, subtract, multiply];
ops[0](5, 3); // calls add

2.5 Test for equality

A first-class object can be compared for equality with other objects of the same type (or with a defined notion of equality). For functions, this often means reference equality (are they the same object?) rather than structural equality, though some languages provide deep comparison.

f = lambda x: x+1
g = f
print(f == g)  # True (same reference)

3 Examples across programming languages

3.1 Functions as first-class objects

3.1.1 JavaScript

JavaScript treats functions as first-class objects. They can be assigned to variables, passed as arguments, returned from functions, and stored in arrays or objects. Anonymous functions (arrow functions or function expressions) are common. This supports higher-order functions like Array.prototype.map, filter, and reduce.

const double = x => x * 2;
[1,2,3].map(double); // [2,4,6]

3.1.2 Python

In Python, functions are objects of the built-in type function. They can be assigned, passed, returned, and stored. The def statement creates a function object; lambda creates an anonymous one. Decorators are a common use of higher-order functions.

def apply_twice(f, x):
    return f(f(x))

apply_twice(lambda n: n + 1, 5)  # returns 7

3.1.3 Lisp

Lisp (and its dialects, especially Scheme and Common Lisp) originated the concept of first-class functions. Functions are created with lambda and can be passed to other functions like mapcar (in Common Lisp) or map (in Scheme). The ability to manipulate functions as data is fundamental to Lisp's metaprogramming.

(defun twice (f x)
  (funcall f (funcall f x)))
(twice #'(lambda (y) (+ y 1)) 5) ;; returns 7

3.2 Other first-class entities

3.2.1 Classes (in some object-oriented languages)

In languages such as Python, Ruby, and Smalltalk, classes themselves are first-class objects. They can be created dynamically, passed as arguments, returned from functions, and stored in variables. For example, in Python, a class can be assigned to a variable:

MyClass = type('MyClass', (), {})
obj = MyClass()

3.2.2 Continuations

A continuation represents the "rest of the computation" at a given point. In languages like Scheme (with call/cc) or Standard ML of New Jersey (with SMLofNJ.Cont), continuations are first-class objects. They can be saved, passed around, and invoked later to resume execution from that point. This enables advanced control structures such as coroutines, exceptions, and backtracking.

4 Semantic implications

4.1 Higher-order functions

A language with first-class functions naturally supports higher-order functions—functions that take other functions as arguments or return them. This allows abstraction over patterns of computation, such as mapping, filtering, folding, and composing. Higher-order functions are a hallmark of functional programming and reduce code duplication.

4.2 Closures

When a function is first-class, it can capture and retain access to lexical variables from its enclosing scope even after that scope exits. This combination of a function with its captured environment is called a closure. Closures enable data hiding, partial application, and callback mechanisms. For example:

function counter() {
    let count = 0;
    return function() { return ++count; };
}
const c = counter();
c(); // 1
c(); // 2

4.3 Metaprogramming

First-class objects, especially functions and classes, enable metaprogramming—writing programs that manipulate other programs (or themselves) at runtime. For example, a function can generate new functions based on configuration, or a class factory can create new types on the fly. In Lisp, macros (which operate on code as data) are a powerful form of metaprogramming that depends on first-class status.

5 Comparison with second-class and third-class citizens

5.1 Second-class objects

Second-class objects can be used only in restricted ways. Typical restrictions: they cannot be passed as arguments, returned from functions, or stored in data structures. In many early languages, functions were second-class: they could be called but not manipulated as values. For example, in C, a function can be passed via a function pointer (which is first-class), but the function itself is not considered first-class because its name cannot appear in all contexts where a value is expected. Similarly, in some languages, integers are first-class but certain built-in types (like labels in assembly or early Basic) are second-class.

5.2 Third-class objects

Third-class objects have even more severe restrictions. They cannot even be named as independent entities; they may only be used in a specific syntactic position. An example is the goto label in many languages (e.g., C's label:). Labels can only appear as the target of a goto statement; they cannot be assigned, passed, or stored. Another example is the concept of "statements" in some languages: they cannot be stored in variables or passed around, unlike expressions.

6 Practical considerations

6.1 Performance overhead

Treating functions and other entities as first-class objects can introduce performance overhead. Closures require additional memory for captured environments, and passing functions as arguments may involve allocation and indirection. In dynamically typed languages, runtime checks for type correctness may also add cost. However, modern compilers (e.g., V8 for JavaScript) and just-in-time (JIT) optimizations can mitigate many of these overheads through techniques like inlining, escape analysis, and optimization of closure representations.

6.2 Language design trade-offs

The decision to make particular entities first-class involves trade-offs. First-class status increases expressiveness and enables powerful abstractions, but it can complicate language semantics, type systems, and garbage collection. For example, making continuations first-class requires copying the entire call stack, which is expensive and makes memory management complex. Consequently, many languages limit continuations to delimited forms or avoid them altogether. Similarly, making classes first-class in a statically typed language requires a more flexible type system (e.g., structural typing or type traits). Language designers balance these factors based on the intended domain and programming paradigm.

7 See also

  • Higher-order function
  • Closure (computer programming)
  • Function (programming)
  • Reification (computer science)
  • Lambda calculus
  • Polymorphism (computer science)

8 References

  1. Strachey, Christopher. "Fundamental Concepts in Programming Languages." Lecture notes, 1965. Reprinted in *Higher-Order and Symbolic Computation* 13, no. 1/2 (2000): 11–49.
  2. Abelson, Harold, and Gerald Jay Sussman. *Structure and Interpretation of Computer Programs*. MIT Press, 1985.
  3. Pierce, Benjamin C. *Types and Programming Languages*. MIT Press, 2002.
  4. Flanagan, David. *JavaScript: The Definitive Guide*. 7th ed. O'Reilly Media, 2020.
  5. Van Rossum, Guido. "Python Reference Manual." Python Software Foundation, 2023.