- Learn
- Computer Science & IT
- Computer Science
- Introduction to Computer Science
- Programming Languages and Paradigms
Programming Languages and Paradigms
BeginnerLesson 5 of 7 in Introduction to Computer Science Course
What you will learn
- Follow a program from the code a person writes down to the binary instructions a processor carries out, and compare the ways that translation can happen: ahead of time, while the program runs, or in a mix of both.
- Compare machine code, assembly language and high-level languagesDescribe the three levels at which programs can be written, show how one line of high-level code maps onto several machine instructions, and explain why most software is written at the highest level.
- Explain what a compiler doesDescribe how a compiler translates a whole program ahead of time, name its main stages, and explain what happens to errors and to the finished executable.
- Explain interpreters, bytecode and just-in-time compilationDescribe how an interpreter runs a program, how languages such as Java and Python combine compiling and interpreting, and how to weigh the trade-offs between these approaches.
- Compare machine code, assembly language and high-level languages
- Separate the rules for how code must be written from the rules for what it does, recognize the building blocks that most high-level languages share, and trace short programs to predict their results.
- Distinguish syntax from semanticsExplain the difference between the form of a program and its meaning, tell syntax errors, runtime errors and logic errors apart, and spot where languages give the same-looking code a different meaning.
- Trace the shared building blocks of high-level programsRecognize the constructs found in nearly every high-level language (variables, expressions, control flow and functions with their scope) and follow a short program step by step to predict its output.
- Distinguish syntax from semantics
- Describe how languages classify and check values by type, where a running program keeps its data, and how memory is reserved and released, by the programmer or automatically, with the safety and speed trade-offs each choice brings.
- Compare how languages use and check data typesExplain what a data type is, compare static and dynamic typing and strict and loose conversion rules, and predict how common languages treat mixed types and number limits.
- Describe where a running program keeps its dataExplain variables as named memory locations, the roles of the call stack and the heap, and the difference between copying a value and sharing a reference.
- Compare manual and automatic memory managementExplain how memory is released in languages where the programmer manages it and in languages with garbage collection or ownership rules, and connect memory errors to program crashes and security flaws.
- Compare how languages use and check data types
- Describe programs as sequences of commands that change stored state, then as collections of objects that bundle data with behavior, and use classes, encapsulation, inheritance and polymorphism to organize code.
- Describe the imperative and procedural styleExplain imperative programming as step-by-step commands that change a program's state, show how procedures and structured control flow organize those commands, and recognize the risks of shared changing state.
- Model data and behavior with classes and objectsExplain how object-oriented programming groups data and the operations on it into objects, read and design a simple class, and use encapsulation to protect an object's data.
- Reuse and extend classes with inheritance, polymorphism and compositionUse inheritance to build specialized classes, explain how polymorphism lets one call behave differently for different objects, and decide when to combine objects instead of inheriting.
- Describe the imperative and procedural style
- Build programs from functions that return new values instead of changing old ones, and write programs that state the result wanted while a language engine decides how to compute it.
- Build programs from pure functions and immutable dataExplain pure functions, immutability, first-class and higher-order functions, and recursion, and rewrite a loop as a pipeline of
map,filterandreduce. - Describe the result with declarative languagesExplain declarative programming as stating what result is wanted rather than how to get it, and read examples in a database query language, a logic language and other declarative notations.
- Build programs from pure functions and immutable data
- Solve the same problem in different styles, recognize that most modern languages mix paradigms, and weigh the technical and practical factors that decide which language suits a project.
- Solve one problem in several paradigmsWrite the same small task in imperative, object-oriented, functional and declarative styles, compare what each version makes easy, and recognize multi-paradigm languages.
- Choose a programming language for a taskWeigh the factors that decide which language suits a project (platform, domain, performance, safety, ecosystem and people) and justify a choice for a concrete scenario.
- Solve one problem in several paradigms
Programming Languages and Paradigms study guide
1From source code to running instructions
A processor runs small binary instructions, while people usually write programs in languages built around the problem they want to solve. Translators connect these two levels. Follow the same calculation through those layers, then compare building a program before it runs with interpreting it or compiling parts during execution. The route affects portability, startup time, speed, and when an error can appear.
1.1 Three levels of program code
A line such as x = a + b names a calculation without mentioning a processor register. Abstraction makes this possible: a language hides lower-level details so that a programmer can focus on the work the program should do.
What the processor runs
Machine code consists of instructions encoded as binary numbers. A processor's instruction set architecture (ISA) defines its instructions and their encodings. x86-64 is common in PCs and servers; ARM is common in phones, tablets, and many laptops. Code built for one ISA needs translation or emulation to work on another.
Hexadecimal makes instruction bytes shorter to display. On x86-64, B8 01 00 00 00 puts 1 into the register EAX. The bytes 01 D8 add the value in EBX to EAX. Registers are small storage locations inside the processor. Each instruction performs a small action, such as moving data, adding, comparing, or jumping. The fetch-decode-execute cycle runs these instructions.
Names for the same small actions
Assembly language replaces instruction bytes with readable names called mnemonics. Those two x86-64 instructions can be written:
mov eax, 1
add eax, ebx
An assembler converts assembly into machine code. An ordinary instruction line normally produces one machine instruction. Register names and instructions still belong to a particular ISA, so using words instead of bytes does not make assembly portable.
Describe a problem in fewer lines
A high-level language provides variables, expressions, conditions, loops, and functions. Python, C, Java, and JavaScript let the programmer describe operations without selecting registers. One statement commonly requires several machine instructions. A simplified translation of x = a + b would:
- Load the value of
ainto a register. - Load the value of
binto another register. - Add the register values.
- Store the result for
x.
A real compiler may combine or rearrange those steps while keeping the required behavior.
| Level | What a programmer writes | What handles the hardware details? |
|---|---|---|
| Machine code | Encoded processor instructions | The programmer or code generator |
| Assembly | Mnemonics and registers | The programmer, with an assembler encoding instructions |
| High-level code | Variables and operations on the problem's data | A compiler, interpreter, or a combination |
High-level source is easier to read, change, and check. Portable C source can be compiled once for an x86-64 system and again for an ARM system. Each build still needs the right operating-system support and libraries. Portability of source does not make a native executable universal.
Why the lower levels still matter
Assembly remains useful in startup code, some operating-system and driver routines, small embedded systems, and security analysis of programs with no available source. For ordinary application code, modern compilers usually produce machine code with performance comparable to handwritten assembly.
The shift toward higher levels happened gradually. Assembly spread in the late 1940s and early 1950s. Grace Hopper's A-0 system in 1952 helped advance automatic translation. IBM released FORTRAN in 1957; COBOL followed in 1959, and Lisp emerged around 1958–1960. "High-level" names the distance from hardware details, not the difficulty of learning the language.
Key Takeaways
- A processor executes machine instructions defined by its ISA.
- An assembler encodes assembly instructions; assembly remains tied to an ISA.
- High-level statements hide hardware steps and commonly become several instructions.
- Portable source can be translated for different targets; native machine code is target-specific.
- Low-level code remains useful where hardware access or analysis requires it.
1.2 How a compiler builds a program
A compiler translates source code into target code. The target may be native machine code or an intermediate form such as bytecode. Ahead-of-time (AOT) compilation happens before the program runs; just-in-time compilation happens during execution.
From text to instructions
For an ahead-of-time build, several stages turn a source file into instructions:
- Lexical analysis splits text into tokens: meaningful pieces such as names, operators, and numbers.
- Parsing checks the grammar and builds a structure showing how the pieces fit together.
- Semantic analysis checks rules about meaning, including declarations and allowed type combinations.
- Optimization finds a more efficient way to perform the required work.
- Code generation emits the target instructions.
Consider the C statement total = price * 3;. Its six tokens are total, =, price, *, 3, and ;. The names are identifiers, 3 is an integer literal, and the semicolon ends the statement. Parsing records that multiplication belongs inside the assignment: calculate price * 3, then store its value in total.
These stages have different jobs. A correctly spelled sequence of tokens can still violate the grammar. A grammatical statement can still misuse a type. Finding tokens is not enough to establish that a program is valid.
Make the same work cheaper
Suppose source code contains seconds_per_day = 60 * 60 * 24. The factors are constants, so the compiler can multiply 60 by 60 to get 3600, then multiply by 24 to get 86400 during the build. It can place the final value in the output instead of repeating the multiplication at run time. This is constant folding.
An optimization must preserve the behavior the language requires. It cannot replace a calculation with a faster one that gives different required results. The aim is to reduce work, time, or memory while keeping the program's meaning.
Join the pieces
Large programs are usually built from many files. A compiler can produce an object file for each source file. An object file contains translated code that may still need connections to other code. A linker joins the pieces and resolves calls to library routines, such as printing or math functions.
| Linking method | Where the library code comes from at run time |
|---|---|
| Static linking | Code included in the executable during the build |
| Dynamic linking | A compatible shared library loaded at startup or during execution |
The resulting executable is built for a target platform, including an ISA and operating-system conventions. The user does not need the ahead-of-time compiler to run it. Shared libraries or runtime components can still be required.
Understand what a successful build proves
In Java, int count = "five"; fails a type check before execution. Text cannot initialize that integer variable. The program must pass the required compile-time checks before a runnable build is available.
Other faults survive compilation. Division by a user-entered zero depends on runtime data. A program intended to subtract that instead adds can run and return the wrong value. Compilation checks language rules; testing must also check that the result matches the intended task.
Key Takeaways
- Compilation translates source into target code, which need not be machine code.
- Compiler stages check tokens, structure, and meaning before producing target instructions.
- Optimization preserves required behavior; constant folding evaluates constants during the build.
- A linker connects object files and library code.
- A successful build can still contain runtime failures or logic errors.
1.3 Interpretation, bytecode, and JIT compilation
A program does not have to become a native executable before a user starts it. An interpreter carries out the program through another running program. Many implementations combine interpretation with compilation, so the useful question is how this particular system executes code.
Execute through an interpreter
An interpreter needs to be available whenever it runs the program. An interactive REPL repeats four actions: read an entry, evaluate it, print a result, and loop for the next entry. It gives quick feedback while trying small expressions.
Interpreting does not necessarily mean reading raw source one line at a time. CPython, the standard Python implementation, first compiles a file into bytecode. A software virtual machine then executes those internal instructions. Bytecode belongs to that runtime; it is not the processor's native instruction set.
Why some errors appear earlier
Compare two Python files. This one has valid syntax:
print("start")
x = 10
print(x + "5")
It prints "start", binds x to 10, and then raises TypeError. Adding that integer to a string fails only when the expression is executed. A similar operation in an unvisited branch can remain unnoticed until the branch runs.
Now remove the closing parenthesis from the last line. CPython cannot compile the file because its syntax is incomplete. It reports a syntax error before any statement in that file executes, so "start" is not printed. Syntax checking of the whole file and runtime checking of an executed operation happen at different times.
Share bytecode, specialize while running
Java shows a common hybrid route. javac Hello.java produces Hello.class, which contains bytecode. java Hello starts a compatible Java Virtual Machine (JVM). Different JVM implementations let that bytecode run on different processor families. The bytecode is portable across compatible runtimes; each runtime must itself work on its host platform. C# similarly targets an intermediate language used by .NET.
A just-in-time (JIT) compiler generates machine code while the program runs. Java's HotSpot runtime commonly starts with interpretation and compiles frequently used methods. Browser JavaScript engines also combine execution techniques, including JIT compilation. Compiling busy parts can improve sustained speed, but uses time and memory during execution.
| Route | Useful advantage | Cost to consider |
|---|---|---|
| Ahead-of-time native build | Work is translated before launch; no compiler needed by the user | Separate compatible builds for target platforms |
| Interpretation | Quick edit-and-run feedback with an installed interpreter | Repeated runtime work can slow compute-heavy code |
| Bytecode with a VM and JIT | Portable intermediate code with runtime optimization | Runtime installation, warmup, and memory overhead |
A loop adding one billion values typically runs much faster in compiled C than in CPython: CPython performs interpreter work and type checks around many operations. For a script renaming 20 files, development effort may matter far more than that difference. Measure the workload before treating a language label as a speed prediction.
Compiled and interpreted describe implementation choices. CPython uses bytecode interpretation; PyPy uses JIT techniques. A language can have more than one implementation, and one implementation can use several execution methods.
Key Takeaways
- An interpreter executes a program and must be present to run it.
- CPython compiles a file to bytecode before executing it.
- A syntax error can prevent a file from starting; a runtime error appears when its operation executes.
- A JIT compiler creates native code during execution, often for frequently used parts.
- Execution methods belong to implementations, and performance depends on the workload.
2Read what short programs do
Reading code involves two questions: whether its form follows the language rules and what its operations mean. The same-looking expression can behave differently in two languages. Learn to classify errors, then trace assignments, decisions, loops, and function calls in execution order. A small table of changing values makes the difference between what a programmer intended and what the program actually does visible.
2.1 Syntax, meaning, and three kinds of error
Syntax sets the rules for valid program text. Semantics gives that text its meaning. A program can follow every grammar rule and still calculate the wrong result, so checking its appearance is only the first part of reading it.
Recognize the pieces and the grammar
Tokens include keywords such as if, identifiers such as total, literals such as 3 or "hello", operators such as + and ==, and punctuation. A grammar describes how these pieces can form expressions and statements.
Python requires a colon after if x > 3: and uses indentation to mark the statements controlled by that condition. C, Java, and JavaScript use curly braces for blocks; C and Java also require semicolons after many statements. In those languages, indentation helps a human reader but does not select the block.
Different syntax can express a similar action. These statements print a greeting:
print("hello")
printf("hello\n");
System.out.println("hello");
The C escape \n adds a newline. Knowing the idea of printing helps when learning each language's way of expressing it.
The same symbols can mean different things
| Expression and language | Result | Rule |
|---|---|---|
7 / 2 in Python or JavaScript | 3.5 | Division produces a fractional result |
7 / 2 with integer operands in C or Java | 3 | Integer division truncates toward zero |
-7 // 2 in Python | -4 | Floor division rounds down |
-7 / 2 with integers in C | -3 | Truncation moves toward zero |
"3" * 4 in Python | "3333" | A string is repeated |
"3" * 4 in JavaScript | 12 | Text is converted to a number for multiplication |
A symbol's familiar appearance does not determine its meaning across languages.
Evaluation order matters too. Precedence makes multiplication and division happen before addition. With a = 4 and b = 6, average = a + b / 2 divides 6 by 2 first, then adds 4, producing 7.0. Parentheses in average = (a + b) / 2 make the sum happen first and give 5.0.
Tell errors apart
A syntax error breaks the grammar, such as a missing Python colon. A runtime error occurs during an executed operation, such as Python division by zero. A logic error allows execution but gives an unintended answer, as in the incorrect average.
Some valid expressions deliberately skip an operation. In Python, x != 0 and 10 / x > 2 uses short-circuit evaluation. When x is zero, the first condition is false, so the right side is not evaluated and no division occurs. Reversing these conditions would remove that protection. Python writes and, or, and not; C, Java, and JavaScript use &&, ||, and !.
Assignment and comparison are another important distinction. = stores a value; == tests equality. C permits if (x = 5): it assigns 5 and treats that nonzero value as true. Python rejects if x = 5: as invalid syntax. The same mistake can therefore be caught at a different stage in different languages.
Key Takeaways
- Syntax defines valid form; semantics defines meaning.
- Operators can have different meanings in different languages.
- Precedence and parentheses determine which operations are grouped.
- Syntax, runtime, and logic errors require different kinds of checks.
- Short-circuit evaluation can skip an unsafe operation.
- Assignment stores a value; equality comparison tests values.
2.2 Trace values through control flow and calls
A short program is easier to understand when you follow one executed statement at a time. Keep the current values visible instead of jumping from the first line to a guessed result.
Expressions and assignments
An expression produces a value: price * 3, x > 10, and len(name) are examples. A statement performs an action, such as assigning a value or controlling a block. In total = price * 3, the right-hand expression is evaluated before its result is assigned to total.
A variable is a name that refers to a value. Assignment gives the name its current value; it is not a mathematical claim that two sides have always been equal. If count holds 4, count = count + 1 first reads 4, adds 1, and binds count to 5.
Follow the next executed statement
Three structures organize control flow:
- Sequence runs statements in order.
- Selection uses a condition to choose a path, such as
ifandelse. - Iteration repeats a block with a
fororwhileloop.
The structured program theorem, associated with Böhm and Jacopini in 1966, shows that these structures can express any algorithm. A for loop commonly visits the items of a collection. A while loop repeats while its condition remains true.
Consider this Python loop:
total = 0
for n in [3, 8, 5]:
total = total + n
The indented assignment runs once for each item. A trace table records the state after each update:
| Moment | Current n | total |
|---|---|---|
| Before the loop | No item yet | 0 |
| First update | 3 | 3 |
| Second update | 8 | 11 |
| Third update | 5 | 16 |
Each new total uses the previous one. Resetting total inside the loop would produce a different calculation because earlier additions would be lost.
Enter and leave a function
A function groups a task under a name. A parameter is a name in the definition; an argument is a value supplied by a call:
def area(width, height):
return width * height
result = area(3, 4)
The call binds width to 3 and height to 4. The multiplication produces 12, and return sends it to the caller. Execution continues at the assignment, so result receives 12. The names describe one task that can be reused with other arguments.
A name has a scope
Scope determines where a name can be used. Names created inside a function are ordinarily local to that call. A top-level name is global to its module. Separate calls have separate local bindings even when they use identical names.
count = 7
def reset():
count = 0
reset()
print(count)
This prints 7. The assignment inside reset creates a local count; it does not replace the global binding. A function can instead return a result and let its caller assign that result.
Scope and lifetime answer different questions. A local name is not available to outside code just because the object it referred to still exists. Returned objects can remain alive, and a returned nested function can keep access to variables from its enclosing call; that retained environment is part of a closure.
Key Takeaways
- Evaluate the right-hand expression before updating an assignment target.
- Trace sequence, selection, and iteration in the order actually executed.
- Parameters are definition names; arguments are supplied values.
- A return value passes a result back to the caller.
- Local and global names can have separate bindings; scope differs from lifetime.
3Types, references, and memory
Values need a type, a place in memory, and a lifetime. These choices explain why text that looks numeric may reject arithmetic, why two names can expose the same changed list, and why unused data sometimes remains allocated. Compare the checks languages perform, follow a function call through memory, and examine three ways to release storage. Each approach changes the balance of control, convenience, and safety.
3.1 Types decide which operations make sense
A data type specifies possible values and the operations supported on them. An integer supports whole-number arithmetic; a Boolean represents true or false. Strings, lists, records, and objects combine or organize other values. Types also guide how values are represented in memory, although a value's size may depend on the value and implementation.
When are types checked?
| Approach | When checks happen | Example |
|---|---|---|
| Static typing | Before execution | Java rejects int count = "five"; |
| Dynamic typing | During execution | Python checks whether an executed operation fits its operands |
In Python, count = 5 followed by count = "five" is valid. The name now refers to a string. Evaluating count + 1 then raises TypeError. Dynamic typing gives values definite types; it does not remove types.
Static typing does not require a visible type label on every variable. Type inference lets a compiler derive a type from surrounding code. Kotlin infers an integer for val n = 5, and Rust can infer an integer for let n = 5;. Python's optional type hints are different: separate tools such as mypy can check them, but ordinary Python execution does not enforce them.
When are values converted?
Checking time and conversion strictness are separate questions. Python rejects "3" + 4; explicit conversion in int("3") + 4 gives 7. JavaScript applies different automatic conversion rules for different operators:
"3" + 4gives the string"34"."3" * 4gives the number12."3" - 4gives the number-1.
Automatic conversion is called coercion. The labels "strong" and "weak" typing are informal ways to discuss conversion strictness, not a second name for static and dynamic typing. Python is dynamic and strict about the mixed addition above; C is static and still permits many implicit numeric conversions.
Python's input() returns a string. If a person types "30", the result still has type str. age + 1 fails, while int(age) + 1 gives 31 when age holds that text. What appears on screen is not enough to determine the type.
Numbers have representation limits
A signed 32-bit integer represents values from −2,147,483,648 through 2,147,483,647. Java's int wraps from the maximum to the minimum when 1 is added. C signed overflow is undefined behavior, so that same wraparound must not be assumed in C. Python integers grow as memory permits: 2 ** 100 is exactly 1,267,650,600,228,229,401,496,703,205,376.
Floating-point numbers represent most decimal fractions approximately in binary. In Python and JavaScript, 0.1 + 0.2 produces 0.30000000000000004. Comparing that result directly with 0.3 gives false.
When a calculation allows a small error, compare values using a tolerance chosen for the task, for example abs(a - b) < 1e-9. An absolute tolerance this small suits only some scales. For exact decimal accounting, use whole smallest units or a suitable decimal type. Changing the comparison cannot make an approximate representation exact.
Key Takeaways
- A type defines values and supported operations.
- Static and dynamic typing describe checking time, while coercion concerns conversion rules.
- Type inference supports static typing; Python hints need separate checking tools.
- Fixed-width integers can overflow, with language-specific behavior.
- Floating-point rounding affects equality tests and the choice of number representation.
3.2 Follow names, objects, and function calls in memory
Main memory stores a running program's data. Each byte has an address. Languages provide names so that programmers can work with values without writing raw addresses for every operation.
Calls use frames; objects have lifetimes
In C, an int commonly occupies 4 bytes, and &x obtains the address of x. Higher-level languages often hide addresses behind references. The important distinction is between a binding that names an object and the object itself.
The call stack tracks active function calls. Each call has a stack frame with information such as parameters, local bindings, and where execution should continue after return. If main calls area(3, 4), an area frame is added above the caller's frame. The function calculates 12, returns, and its frame is removed. For ordinary nested calls, the newest call returns first.
Recursion adds a call for each unfinished level. If calls keep accumulating, available stack space can run out. Python normally raises RecursionError after roughly 1,000 nested calls, with the limit depending on its settings. A stopping condition must be reached for a finite recursive calculation to finish.
The heap holds dynamically allocated objects, such as a list that grows or an object that outlives the function that created it. Allocation reserves storage. A returned list can remain on the heap after its creating call has ended.
Copying a reference shares an object
A reference identifies an object. A pointer in C or C++ explicitly represents an address used to access data. Copying a reference copies access to the object, not all the object's data.
a = [1, 2]
b = a
b.append(3)
After the first line there is one list. The second line binds another name to it. The last line mutates that list, so both a and b show [1, 2, 3]. This sharing is aliasing. Using b = a.copy() would create a separate outer list; for this list of integers, appending to the new list would leave a unchanged.
Contrast assignment with mutation:
x = 5
y = x
y = y + 1
The integer 5 is immutable. Addition produces 6, and only y is rebound. x still refers to 5. Python numbers, strings, and tuples are immutable; lists and dictionaries are mutable. A tuple's fixed contents can include a reference to a mutable list, so immutability of the outer container does not freeze every nested object.
Apply the same rules to arguments
When Python passes a list to a function, the parameter refers to the same object. Calling items.append(4) inside the function changes that list. Assigning items = [] binds only the local parameter name to a new list. The caller's name remains bound to the original object.
C passes argument values by value. To let a function update a caller's variable through its address, pass a pointer to it. In each language, ask two separate questions while tracing: "Which object does this name refer to?" and "Does this operation change that object or bind a name to something else?"
Key Takeaways
- The call stack tracks unfinished calls; the heap holds dynamically allocated objects.
- An object can outlive the function that created it.
- Copying a reference shares an object; it does not duplicate its data.
- Mutation changes an object, while rebinding changes what a name refers to.
- Argument passing follows the same distinction between shared objects and local bindings.
3.3 Release memory without losing live data
Allocated memory must eventually become reusable. The challenge is releasing data after its last use while keeping every object the program still needs. Languages assign this responsibility to the programmer, a runtime system, or compiler-checked ownership rules.
Manual management and its failures
In C, malloc(n) requests n bytes and returns a pointer to the block if successful. free(p) releases a block obtained by allocation. The programmer must use it at the right time and exactly once. C++ also supports manual management, although its standard tools can automate release through object lifetimes.
Several mistakes have different causes:
| Fault | What went wrong |
|---|---|
| Memory leak | Unneeded allocated storage is not released |
| Use after free | A pointer is used after its target storage was released |
| Double free | The same allocated block is released twice |
| Buffer overflow | A write goes beyond the bounds of a memory block |
A pointer left referring to released storage is dangling. The storage might be reused for something else, so accessing it can corrupt data or crash the program.
For a C array int scores[10], valid positions are 0 through 9. Writing to scores[10] goes beyond the array, and C provides no automatic bounds check. Python raises IndexError when index 10 is used on a ten-item list, preventing that access from overwriting adjacent memory.
Trace reachability or count references
A tracing garbage collector starts from live roots, such as active references, follows links between objects, and can reclaim unreachable objects. Java, C#, Go, and JavaScript use tracing approaches. Collection requires runtime work and memory; depending on the collector, it can introduce pauses.
Reference counting tracks references to an object. For a fresh Python list, the simplified sequence is: a = [1, 2] supplies one reference; b = a supplies a second; del a removes one; b = None removes the other. CPython normally reclaims this ordinary list when its count reaches zero.
A group of objects can keep one another's counts above zero even when no live part of the program can reach the group. This is a reference cycle. CPython supplements reference counting with cycle collection. Reference counting and tracing are both automatic memory-management techniques.
Check ownership before execution
Rust gives each value an owner and checks rules for borrowing references. When ownership ends, generated cleanup code releases the value. Safe Rust rejects uses of references that could outlive their valid target, without requiring a tracing garbage collector. Swift uses automatic reference counting inserted by the compiler.
Memory safety matters beyond crashes. Microsoft reported in 2019 that roughly 70% of the vulnerabilities it assigned CVEs to each year involved memory safety; Chromium reported a similar share of serious bugs in 2020. In December 2023, CISA, NSA, FBI, and international partners urged software makers to plan transitions toward memory-safe languages.
Automatic reclamation still retains reachable objects. An ever-growing global list can keep every old record alive even after the application has finished using it. A collector cannot decide that these reachable records have become unnecessary to the task; the program must stop retaining them.
Key Takeaways
- Manual allocation requires correctly timed release.
- Leaks, use after free, double free, and buffer overflow have distinct causes.
- Tracing finds unreachable objects; reference counting reacts to lost references.
- CPython adds cycle collection to reference counting.
- Ownership can arrange release without a tracing collector.
- Automatic collection cannot reclaim objects that the program still retains.
4Organize changing state and cooperating objects
One way to describe a program is as commands that update values. Another groups those values with the operations that control them. These views often work together: an object method can contain an ordinary loop. Trace changing state first, then build a small account model to see how classes protect rules and how inheritance, polymorphism, and composition let related objects share an interface without taking on the wrong responsibilities.
4.1 Commands, procedures, and shared state
A programming paradigm is a way to organize code and think about computation. In imperative programming, a program gives commands that change its state: the values currently held by variables and objects.
Make the state changes visible
A running total is a simple imperative calculation:
total = 0
for n in [3, 8, 5]:
total = total + n
The initial state is total = 0. Adding 3 changes it to 3. Adding 8 changes it to 11. Adding 5 changes it to 16. Each assignment uses the state left by the previous step, so order is part of the method.
This style matches the processor's step-by-step changes to registers and memory. It also fits algorithms that deliberately update a collection in place, such as a sorting routine that exchanges items.
Give tasks names
Procedural programming organizes imperative code into functions or procedures. A report program might call read_scores(), average(scores), and print_report(avg). The names let a reader see the overall task without following every internal line at once. Each procedure can be understood and tested separately.
C, Pascal, Fortran, and COBOL are classic procedural languages. Python also supports this style: a script can use named functions without defining any classes.
Structured programming organizes control flow with sequence, selection, loops, and procedure calls. It avoids arbitrary jumps that make the next executed statement hard to predict. Edsger Dijkstra's 1968 letter "Go To Statement Considered Harmful" helped popularize this approach. Controlled exits such as break, continue, and return remain useful: they leave a defined loop iteration, loop, or function.
Effects can extend beyond a return value
A side effect changes something outside a function's returned result. Printing, saving a file, sending a network message, and changing shared data are side effects. Programs need such actions to interact with the world.
Problems arise when unrelated code silently changes the same state. Suppose apply_discount() reduces a global price by 10%, and add_shipping() adds 5. Starting from 100, discounting first gives 90, then shipping gives 95. Shipping first gives 105, then discounting gives 94.5. Both procedures perform their own steps correctly, but the combined result depends on their order.
Passing values and returning new results can make that order visible:
price = add_shipping(apply_discount(100))
The inner call runs first: discount, then shipping. A different nesting expresses a different rule. Clear data flow helps reveal whether shipping should itself receive the discount.
| Organization | Main question while reading |
|---|---|
| Imperative commands | Which value changes next? |
| Named procedures | Which task does this call perform? |
| Shared global updates | Which earlier action changed this value? |
Imperative code is still common inside modern object methods and event handlers. Choosing a broader object-oriented or functional design does not prevent a small part from using direct commands where those steps make the algorithm clear.
Key Takeaways
- Imperative code describes steps that change stored state.
- Procedures give coherent tasks names and boundaries.
- Structured control flow makes execution paths easier to follow.
- Side effects include changes to shared data and interactions with the outside world.
- Shared updates can make results depend on call order.
4.2 Classes keep data with its rules
Object-oriented programming (OOP) models a program as cooperating objects. Each object combines data with operations on that data. An account can hold its owner and balance while offering deposit and withdrawal operations.
Separate the blueprint from its instances
A class defines the attributes and methods its objects provide. An object, or instance, is one particular member of that class. BankAccount is the blueprint; Ana's account with 150 and Ben's account with 40 are distinct instances. Their balances can change independently.
An attribute stores data associated with an object. A method is an operation called through an object, such as acct.deposit(50). self in a Python instance method refers to the object receiving the call.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
self._balance = self._balance + amount
Calling BankAccount("Ana", 100) constructs a new instance. Its __init__ method initializes the instance's data. A constructor is the setup process for a new object; Python separates creation from this initialization step. Calling acct.deposit(50) on that account changes its balance from 100 to 150.
Put the rule at the update point
Encapsulation brings data and the operations that maintain its rules together. Suppose an account must never have a negative balance. The withdrawal method checks the proposed change before making it:
def withdraw(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
if amount > self._balance:
raise ValueError("insufficient funds")
self._balance = self._balance - amount
This method belongs inside BankAccount. From a balance of 150, withdraw(200) raises an error and leaves 150. withdraw(30) succeeds and leaves 120. The failed call does not first subtract and then discover the problem; checking happens before the update.
Other code should use those methods instead of assigning an unchecked balance. Java and C# can restrict direct access with private. Python's leading underscore in _balance signals an internal detail by convention; it does not enforce privacy.
Information hiding goes further: callers depend on the operations a class offers without needing to know its internal representation. The balance could later be stored differently while the public deposit and withdrawal operations retain their meaning.
Let the problem suggest responsibilities
A library system has books, members, and loans. Those nouns suggest possible classes or attributes; actions such as borrowing and renewing suggest methods. Choose one clear responsibility for each class. An account class that also sends every email and formats every report becomes harder to change because unrelated work is mixed together.
Classes and objects appeared in Simula 67 in 1967; Smalltalk helped spread the approach in the 1970s. Java, C#, C++, and Python support OOP, but using one of those languages does not force every program into classes. The useful design question is which data and rules belong together.
Key Takeaways
- A class defines attributes and methods; each instance has its own instance data.
- Methods act on the object receiving the call.
- Encapsulation puts validation and state changes together.
- Python underscores signal internal attributes by convention; some languages enforce private access.
- Information hiding lets callers depend on the public operations instead of its internals.
- A class should have a coherent responsibility.
4.3 Specialize behavior and combine objects
Objects often need to share operations while differing in details. Inheritance, polymorphism, and composition solve different parts of that design problem.
Specialize an existing kind of object
Inheritance defines a subclass from a superclass. In Python, class SavingsAccount(BankAccount): gives the specialized class access to inherited account behavior. It can add add_interest(rate): a balance of 1000 with a 2% interest addition becomes 1020.
The intended relationship is is-a. A savings account is still a bank account and must honor the rules expected of bank accounts. Reusing a few lines is not enough reason to claim that one kind of object is another.
A subclass can override a method with its own implementation. Suppose a checking account charges a fee of 1 for each successful withdrawal. With a balance of 100, withdrawing 30 costs 31 and leaves 69. To preserve the account rule, the method must check the withdrawal plus the fee before changing the balance. If that combined cost exceeds the balance, it refuses the whole operation.
Make one call work with different objects
Polymorphism lets the same operation work across different object types. A circle and rectangle can both provide area():
for shape in shapes:
print(shape.area())
The loop does not need an if for every shape class. Each object supplies its own implementation. Selecting that implementation during execution is dynamic dispatch.
For Circle(2), the area is about 12.57 square units. For Rectangle(3, 4), it is 12 square units. Their combined area is about 24.57 square units. A later Triangle can join the collection by providing a compatible area() operation; the loop itself does not need another branch.
An interface states the operations a type must offer. Java's interface Shape { double area(); } requires implementing classes to supply the area method. An abstract class can provide some shared implementation while leaving other methods for subclasses. Python often uses duck typing: an object can work in the loop if it provides the required operation, even without a shared superclass.
Hold a part instead of becoming it
Composition stores other objects as parts. A car has an engine and wheels. It can call its engine's start() method without inheriting from Engine.
| Relationship | Suitable design | Reason |
|---|---|---|
| Savings account is an account | Inheritance | It preserves the account's expected behavior |
| Car has an engine | Composition | The engine is a component, not the car's category |
| Different shapes offer area | Shared operation or interface | The caller can request the operation uniformly |
Deep inheritance chains can spread the effects of a superclass change through many classes. A subclass that must disable inherited behavior can signal a poor is-a relationship. Composition often keeps such dependencies smaller and clearer.
The four commonly named OOP pillars fit together here: encapsulation protects data through operations, abstraction hides implementation details, inheritance specializes a type, and polymorphism lets a caller use a shared operation across implementations.
Key Takeaways
- Inheritance models an is-a relationship and must preserve expected behavior.
- Overriding replaces a method implementation in a specialized class.
- Polymorphism lets one operation invoke different implementations.
- An interface states required operations; duck typing relies on the operations an object provides.
- Composition models objects that have other objects as parts.
5Compute new values and describe wanted results
A program can focus on transformations of values or on the result an engine should produce. Pure functions make dependencies explicit, while immutable data avoids changes through shared references. Declarative queries let a specialized engine choose execution steps. Follow a data pipeline and a database query to see what these approaches make easier, and where input, output, and concrete machine instructions still enter the picture.
5.1 Pure functions and data pipelines
Functional programming builds results by combining functions. Its central habit is to produce new values from inputs while limiting shared mutation. Lisp introduced widely used functional ideas around 1958; Haskell, Erlang, Clojure, F#, and OCaml develop those ideas in different ways.
Know what makes a function pure
A pure function gives the same result for the same arguments and has no side effects. def with_tax(price): return price * 1.2 illustrates the idea: with_tax(50) gives 60.0. A function that also appends to a global list or prints a message has an effect beyond its returned value. A result that depends on the current time also depends on more than the arguments.
A pure call can be replaced by its returned value without changing program behavior. This property is referential transparency. It makes isolated tests straightforward and permits independent calls to run in parallel without competing to update shared state.
Immutability keeps an existing value unchanged. [n + 1 for n in nums] creates a new list, leaving nums intact. A program can still represent changing information by producing new versions; it does not have to overwrite the old version.
Pass behavior as data
Functions are first-class when they can be assigned to names, passed as arguments, and returned as values. A higher-order function takes a function or returns one. A lambda is an unnamed function expression, such as lambda n: n * n.
Three common higher-order operations form a pipeline:
filterkeeps items that satisfy a test.maptransforms each remaining item.reducecombines items into one result using an accumulator.
To total the squares of even numbers from 1 through 6, filtering leaves 2, 4, and 6; squaring gives 4, 16, and 36; summing gives 56.
sum(map(lambda n: n * n,
filter(lambda n: n % 2 == 0, range(1, 7))))
Here % 2 == 0 tests evenness, and range(1, 7) supplies 1 through 6. Haskell expresses the same calculation as sum (map (^2) (filter even [1..6])).
With reduce imported from Python's functools, reduce(lambda acc, n: acc + n, [3, 8, 5], 0) combines 0 with 3 to get 3, then with 8 to get 11, then with 5 to get 16. The pipeline describes transformations without a surrounding loop updating a shared total.
Preserve an environment or call a smaller problem
def multiplier(k): return lambda x: k * x returns a function that retains access to k. Thus multiplier(3)(10) returns 30 even after the outer call has finished. The returned function and retained environment form a closure.
Recursion has a function call itself on a smaller problem. Factorial returns 1 at n == 0; otherwise it returns n * factorial(n - 1). For 5, the products give 120. A finite calculation needs a reachable base case. Python's unresolved nested calls eventually raise RecursionError; languages with proper tail calls can reuse stack space for calls in final position.
Using functions alone does not make code functional: procedural functions can mutate state. Real functional programs still read input and write output, commonly keeping effects near the program's edges and the central calculations pure.
Key Takeaways
- Pure functions depend only on their arguments and have no side effects.
- Immutable updates create new values.
- First-class functions can be passed and returned; higher-order functions take or return functions.
- Filter selects, map transforms, and reduce combines.
- A closure retains its creation environment.
- A finite recursive calculation needs a base case it actually reaches.
5.2 State a result and let an engine find it
Declarative programming describes the result wanted while leaving execution steps to a language engine. Functional code is commonly grouped under this broad idea, alongside query languages and logic programming.
Read a query as a request
A relational database stores data in tables. Suppose students has the name and grade rows Ana 92, Ben 75, Chen 80, and Dara 64:
SELECT name
FROM students
WHERE grade >= 80
ORDER BY name;
FROM names the table, WHERE gives the condition, SELECT chooses the returned field, and ORDER BY requests alphabetical order. Ana and Chen satisfy the condition, including Chen's grade of exactly 80. The result is their names in that order.
An imperative program would create a result list, visit rows, test each grade, append qualifying names, and sort the list. SQL gives the database the required outcome without writing that loop.
A query optimizer chooses an execution plan. It might scan the table or use an available index, a lookup structure that can speed suitable searches. Many indexes use sorted structures; others use different arrangements. Adding an index can change the plan without changing the query. More data still means work: declaring the result does not guarantee that every query is cheap.
Ask what follows from facts and rules
Logic programming describes facts and relationships, then searches for values satisfying a question. In Prolog:
parent(tom, bob).
parent(bob, ann).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
The rule means X is a grandparent of Z when there is a Y for which both parent relationships hold. The query ?- grandparent(tom, Who). finds Who = ann: Tom is Bob's parent and Bob is Ann's parent. Capitalized names stand for variables; the lowercase names identify particular people.
Recognize declarative tools around you
A spreadsheet formula =SUM(B2:B5) states a dependency. The spreadsheet recalculates it when relevant cells change. The regular expression [0-9]{3}-[0-9]{4} describes three digits, a hyphen, and four digits, as in "555-0199". A text-matching engine performs the search.
CSS states appearance with a rule such as h1 { color: navy; }; HTML marks page structure. They are declarative notations, commonly classified as styling and markup languages. Their role differs from a general-purpose language that can express a wide range of computations.
| Language kind | Task focus | Examples |
|---|---|---|
| Domain-specific language | A particular kind of work | SQL, regular expressions, CSS |
| General-purpose language | Many kinds of programs | Python, Java, Haskell |
Declarative code can stay close to a problem statement and let an engine improve its method. It also gives the programmer less direct control over individual steps, which can make a slow plan harder to diagnose. Concrete instructions still run underneath: declarative describes the programmer's expression of the task, not a processor that works without steps.
Key Takeaways
- Declarative code states a wanted result; an engine chooses execution steps.
- SQL selects rows and fields through conditions and requested ordering.
- An optimizer can change a query plan without changing the query.
- Logic programming derives answers from facts and rules.
- Domain-specific languages target particular tasks; general-purpose languages support many kinds of programs.
6Choose a style and a language for the job
Different program styles can produce the same answer while organizing the work differently. Comparing them on one task makes their trade-offs easier to see. A language choice then adds practical constraints: the target platform, available libraries, memory and speed limits, safety needs, and the people maintaining the code. A project with several parts can use a different language and a different mix of styles for each part.
6.1 One task in several programming styles
A paradigm changes how a solution is expressed, not which numerical answer a correct solution should produce. Consider orders with amounts [120, 45, 300, 80]: find the total of amounts strictly greater than 100.
Follow state changes
The imperative version exposes the loop and accumulator:
total = 0
for amount in amounts:
if amount > 100:
total = total + amount
The first amount changes total to 120. The second leaves it unchanged. The third changes it to 420, and the fourth leaves it there. The program makes the test and update order visible.
Transform a collection
A compact functional-style Python expression is sum(a for a in amounts if a > 100). It selects 120 and 300 and combines them into 420. Another version is sum(filter(lambda a: a > 100, amounts)). Here the predicate describes membership and sum combines the selected values.
A generator expression or filter call uses Python's multi-paradigm features; its presence does not make every surrounding part of the application pure.
Put rules with objects
In an object-oriented design, an Order stores an amount and offers is_large(), which returns self.amount > 100. An OrderBook holds order objects and offers large_total(). That method can loop through orders, ask each whether it qualifies, and add qualifying amounts.
The total remains 420. The difference is where responsibilities live: the rule for a large order belongs to Order, and collection-level work belongs to OrderBook. Other features can reuse the rule without copying its threshold test.
Ask the database
If the data is already in a relational database, SQL can request the total:
SELECT SUM(amount)
FROM orders
WHERE amount > 100;
The database chooses the retrieval and aggregation plan. The query does not instruct a caller to visit every row or keep a local accumulator.
| Style | What the example emphasizes |
|---|---|
| Imperative | Ordered tests and updates to a total |
| Object-oriented | Orders and collection objects with defined responsibilities |
| Functional | Selection and combination of values |
| Declarative SQL | The required result over stored data |
Combine styles where they help
Python supports procedural, object-oriented, and functional techniques. JavaScript provides objects, first-class functions, and closures. C++, C#, Kotlin, Scala, Swift, and Rust also mix styles. Java is strongly associated with OOP but has supported lambdas since Java 8 in 2014; Haskell is purely functional.
A web application can use classes for users and orders, functional transformations for report data, imperative updates inside methods, and SQL for database queries. These choices work at different layers.
Event-driven programming adds another organizing idea: register a handler that runs when an event arrives. In JavaScript, button.addEventListener("click", showMenu) passes showMenu as a function value. A later click invokes it. The handler can itself use objects, pure transformations, or imperative steps. Its event-driven role says when it runs, not the only style its body may use.
Key Takeaways
- Correct solutions in different paradigms can compute the same result.
- A style changes where steps, state, rules, and responsibilities appear.
- Modern languages commonly support multiple paradigms.
- An event handler runs in response to an event and can use other styles internally.
6.2 Choose from constraints and trade-offs
No programming language is the best fit for every project. General-purpose languages are commonly Turing complete: with unbounded time and memory, they can express the same class of computations. Real choices depend on whether a solution fits the actual platform, resources, tools, and people.
Start with what must work
The platform is often a hard constraint. Browsers run JavaScript and can run compiled modules through WebAssembly. TypeScript adds static checking and compiles to JavaScript. Swift is a standard choice for Apple-platform apps; Kotlin is recommended for Android. SQL is the usual language for relational queries.
Next consider the domain and ecosystem. Python has widely used data and machine-learning libraries such as NumPy and pandas. R focuses on statistics. C and C++ are common in operating systems, game engines, and embedded code. Java and C# are common in large business systems; Bash is useful for automating operating-system tasks.
These are practical associations, not exclusive territories. A suitable library can save far more development work than a small difference in language syntax.
Examine resource and safety needs
A microcontroller with only a few kilobytes of RAM may need fine control over memory and runtime overhead. C, C++, or Rust can be suitable where the target supports them. Demanding game or database components often favor those languages too, but engines and resource budgets differ: Unity uses C#, and some databases use Java or Go.
Before choosing for speed, find the bottleneck: the part limiting performance. A program waiting mainly for a network response will not become dramatically faster just because a small local loop is compiled more efficiently. Measure representative work on the target platform.
Memory safety reduces errors such as invalid memory access. Safe Rust checks ownership rules at compile time; managed languages such as Java, C#, Go, and Python provide runtime memory management. Static typing catches many incompatible operations before execution, which can help in a large codebase. Neither memory safety nor static typing proves that the program's logic is correct.
Include the cost of maintaining the result
Team knowledge, available developers, debuggers, test tools, libraries, licenses, and existing code all affect a decision. A familiar, supported language can reduce delivery and maintenance costs. Rewriting a working system creates migration work and risks even if the new language has attractive features.
For a city weather network, different components have different needs:
| Component | Reasonable language choice | Reason |
|---|---|---|
| Small sensor firmware | C or Rust | Memory control and supported hardware targets |
| Stored readings | SQL | Database filtering and aggregation |
| Cleaning and analysis | Python | Data-processing libraries |
| Interactive browser display | JavaScript or TypeScript | Browser support and interface tooling |
A useful justification links each choice to a requirement: "The team already maintains the Java service and its libraries support the new feature." A popularity rank alone cannot supply that reasoning. Surveys can indicate a large community or hiring pool; they do not establish the best language for a particular workload.
Key Takeaways
- Check platform constraints before preferences.
- Libraries and tools connect a language to its problem domain.
- Measure the bottleneck before choosing primarily for speed.
- Memory safety and static checking reduce some faults, while logic still needs testing.
- Team skills, existing code, and maintenance costs belong in the decision.
- Different components can justify different languages.
Programming Languages and Paradigms examples
1Follow a program from source to execution
13 steps
- 1IntroductionOne calculation crosses several layers
You write one calculation, but a processor needs small encoded instructions called machine code. A translator supplies the missing detail.
Follow
x = a + bdownward from source, then follow the program forward through building and running. Keep two questions separate: what form is the code in, and which software is carrying it out? A compiler, an interpreter, and a JIT compiler answer the second one differently. That distinction will explain both portability and the timing of errors. - 2ExampleOne calculation becomes several actions
For
x = a + b, imagine four lower-level actions:- Load the value of
ainto a register. - Load the value of
binto another register. - Add the two register values.
- Store the result for
x.
The high-level statement hides register selection and storage details. Actual generated instructions can differ because a compiler may optimize the work. The required calculation stays the same.
- Load the value of
- 3ExplanationReadable instructions still target a processor
Assembly uses instruction names such as
movandadd. An assembler encodes them as machine instructions.For x86-64,
mov eax, 1puts 1 in EAX;add eax, ebxadds EBX to it. Those registers and encodings belong to that instruction set architecture. Replacing bytes with readable names helps a person, but does not turn the instructions into a portable high-level description. - 4ChallengeMove the program to another processor
A team has portable C source and an x86-64 native executable. It needs an ARM native executable. Which artifact can it use as the starting point, and what work is needed?
---reveal---
Use the source and compile it for the ARM target, with the required platform support. The x86-64 executable contains instructions for a different ISA. Source portability allows another translation; it does not make those existing machine instructions native ARM code.
- 5ExampleIdentify tokens before structure
Consider
total = price * 3;in C.Lexical analysis finds six tokens:
total,=,price,*,3, and;. Parsing then groupsprice * 3as the expression whose result is assigned tototal.These are distinct jobs. Tokenizing identifies the pieces; parsing establishes how they fit. Semantic analysis can then check questions such as whether the names are declared and the operand types are compatible.
- 6ExampleMove constant work into the build
The source expression
60 * 60 * 24uses only constants. Constant folding can evaluate it before execution: first 3600, then 86400.Code generation can use that result instead of instructions that repeat the multiplications. This is an optimization because it changes how the work is performed while preserving the required result. Replacing a value with a merely convenient approximation would not be justified by this rule.
- 7ExplanationConnect translated files to library routines
Compilation can produce separate object files. A linker connects those files and resolves references to library operations.
Static linking includes library code in the built executable. Dynamic linking relies on a compatible shared library loaded later. Neither approach requires the user's machine to have the ahead-of-time compiler, but a dynamically linked executable still needs its runtime dependencies.
- 8ChallengeA successful build gives the wrong answer
A C program builds successfully. Its report should subtract a used quantity from a starting quantity, but the code adds them. Has the compiler failed to check a language rule?
---reveal---
No. Addition can be valid syntax with compatible types while expressing the wrong task. This is a logic error. The compiler checks language rules and generates code; a test with a known expected result is needed to expose the mismatch between the calculation and its intended purpose.
- 9ExplanationA runtime can execute intermediate instructions
CPython compiles a valid Python file into bytecode, then executes that bytecode in its virtual machine. The processor executes the runtime's machine instructions while the runtime carries out the Python operations.
Java also uses bytecode:
javacproduces class files for a compatible JVM. Bytecode belongs to a runtime instruction model, so it should not be mistaken for a universal processor instruction set. - 10ExampleCompare two moments when execution can stop
In this valid Python file, the first line runs:
print("start") x = 10 print(x + "5")It prints "start" before the last line raises
TypeError. Now remove the final closing parenthesis: CPython cannot compile the file, so no statement in that file runs. A syntax failure during preparation and a type failure during an executed operation stop the program at different stages. - 11ExplanationCompile busy code during the run
A JIT compiler produces machine code while the program runs. A runtime can interpret first, observe frequently used methods, and compile those methods.
This shifts costs: compilation uses time and memory during the run, while repeated execution can become faster afterward. A long-running service may benefit more from that warmup than a tiny one-off script. The result depends on the implementation and workload.
- 12ChallengeSeparate bytecode portability from native speed
A Java class file runs through a compatible JVM on two different ISAs. During each run, the runtime compiles a frequently used method. Why can one class file be shared while the generated machine code differs?
---reveal---
The class file contains JVM bytecode, which both compatible runtimes understand. Each JIT targets its own host processor. Portability comes from the shared intermediate format and compatible runtimes; the machine instructions produced during execution remain platform-specific.
- 13RecapKeep levels and execution routes separate
Machine code targets an ISA; assembly gives its instructions readable names; high-level source hides more hardware detail.
A compiler translates code, an interpreter carries it out, and a JIT compiles during execution. A runtime can combine these methods. To explain a real system, identify its source, intermediate and native forms, then say which stage checks syntax and which stage executes the operation that may fail.
2Trace meaning through a short program
10 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionForm and meaning answer different questions
- 2ExampleValid code can group the wrong calculation
- 3ExplanationThe first condition can protect the second
- 4ChallengePredict whether the division runs
- 5ExplanationAn assignment reads before it updates
- 6ExampleTrace each accumulator update
- 7ExampleFollow a call and its returned value
- 8ChallengeA local update can leave the caller unchanged
- 9ChallengeFollow grammar and state together
- 10RecapRead the executed path
3Track types and shared objects
14 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow values, objects, and storage
- 2ExplanationTyped values can move between names
- 3Key InsightInference and conversion answer different questions
- 4ChallengeRead digits as the value actually returned
- 5ExplanationRepresentation affects numeric results
- 6ExplanationA call frame can end before an object does
- 7ExampleTwo names point to one list
- 8ChallengeA parameter can mutate and then move away
- 9ExplanationIdentify exactly how storage is misused
- 10ExampleFollow references until the last one is gone
- 11Key InsightA cycle can have no live entry point
- 12ExplanationOwnership checks access before the run
- 13ChallengeFind why collection cannot reclaim a list
- 14RecapTrack three different decisions
4Protect rules and choose object relationships
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionSteps need reliable boundaries
- 2ExampleOperation order changes shared state
- 3ExplanationMake a procedure boundary useful
- 4ChallengePredict a reordered update
- 5ExplanationGive each account its own state
- 6ExampleValidate before changing a balance
- 7ChallengeLocate a bypassed rule
- 8ExampleSpecialize without breaking the contract
- 9ExplanationLet the object choose its area method
- 10ConnectionHold the component that does the work
- 11ChallengeChoose two relationships in a new design
- 12RecapMatch boundaries to responsibilities
5Transform a collection and query a result
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionState a transformation or a request
- 2ExplanationPurity describes more than the returned number
- 3ExampleSelect the even values first
- 4ExampleTransform and combine the selected values
- 5ChallengeApply the pipeline in a new case
- 6ExplanationReturn behavior that remembers its factor
- 7ExampleReduce the problem until a direct case applies
- 8ExampleRead SQL as a request over rows
- 9Key InsightKeep the result while changing the plan
- 10ExampleConnect two facts through a rule
- 11ChallengeRead a new query including its boundary
- 12ChallengeCombine a query with a pure transformation
- 13RecapKeep the input-to-result relationship visible
6Compare styles and justify a language choice
10 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionKeep the task fixed
- 2ExampleKeep the task fixed while changing the style
- 3ExamplePlace the same rule in objects or a query
- 4ChallengeDecide whether a design has one exclusive style
- 5ExplanationEliminate unsupported targets first
- 6ExampleChoose for a complete weather system
- 7TipMeasure what consumes the time
- 8ChallengeDefend a choice with competing constraints
- 9ChallengeConnect language choice to program organization
- 10RecapGive a reason tied to the requirement
Programming Languages and Paradigms practice questions
Set 1: Read code and identify the execution route10 Practice Questions
Question 1 of 10
mov and jmp. At what language level is the program now written?Show answerHide answer
Question 2 of 10
Show answerHide answer
Question 3 of 10
Show answerHide answer
Question 4 of 10
Show answerHide answer
Question 5 of 10
What final value does this Python code store in total?
total = 1
for n in [4, 7, 2]:
total = total + n
Show answerHide answer
Question 6 of 10
label = "18" is followed by label = 18. The name can change type, but "18" + 2 raises an error. How are these two facts described?Show answerHide answer
Question 7 of 10
What does first contain after this Python code runs?
first = [4, 8]
second = first
second.append(6)
Show answerHide answer
second mutates that shared list, so first also shows the added 6. Keeping [4, 8] would require a separate list, such as one created by copying before the append.Question 8 of 10
read_data(), calculate_total(), and save_report(), each containing ordered commands. What organization does this illustrate?Show answerHide answer
Question 9 of 10
n == 0; otherwise it calls itself with n + 1. Starting at positive 2, why does the written base case not make the calculation finish?Show answerHide answer
Question 10 of 10
Show answerHide answer
Set 2: Apply type, object, and query rules10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
a = 8 and b = 12 in Python, what values do a + b / 2 and (a + b) / 2 produce, respectively?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
balance attribute and a deposit method. Two accounts are created with different starting balances. Where do those distinct balances belong?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
[1, 4, 7, 10], a pipeline keeps values greater than 3, subtracts 1 from each retained value, then sums them. What is the result?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What are x and y after this Python code?
x = [12]
y = x
x = x + [3]
y.append(6)
Show answerHide answer
Save this lesson to see this answer.
Set 3: Trace calls, effects, and object contracts10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
minutes = 60 * 12 with the precomputed value 720. What optimization is used, and what must it preserve?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does this Python program print?
score = 11
def change():
score = 4
return score + 2
print(change())
print(score)
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What is items after the call?
items = [3]
def revise(values):
values.append(8)
values = [1]
values.append(5)
revise(items)
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
showDetails for a click event. Why can that same handler still use object methods and imperative assignments?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Reason across execution and design boundaries10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
n = 0 in Python, compare n != 0 and 36 / n > 4 with 36 / n > 4 and n != 0. What happens?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What final value is stored in total?
def bump(x):
return x + 2
total = 0
for n in [2, 5, 8]:
if n > 3:
total = total + bump(n)
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
item.render(). A new unrelated class supplies a compatible render() method. No superclass is required by the Python caller. Why can the object join the loop without a type-specific branch?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 5: Diagnose limits and combine the ideas10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does this Python program print?
value = 6
def step(value):
value = value + 3
return value
for n in [1, 2]:
result = step(value)
print(value, result)
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
double() multiplies it by 2; add_three() adds 3. Starting from the same value, what is always the difference between double-then-add and add-then-double?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
readings has rows (level, active): (50, true), (65, true), (20, false), (85, true), and (90, false). A query requests SUM(level) where active = true AND level > 50. After a suitable index is added, what total is required, and may the execution plan change?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
parent(lea, noah). and parent(noah, sora). plus the rule grandparent(X, Z) :- parent(X, Y), parent(Y, Z).. What does grandparent(lea, Who) find?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
[50, 65, 20, 85]. What must their correct results be?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Abstraction
Ahead-of-time compilation
Aliasing
b = a, aliasing explains why appending through b can also change what is seen through a.Allocation
Assembler
add eax, ebx into the corresponding x86-64 instruction bytes.Assembly language
Assignment
count = count + 1 reads the old count before replacing it with the incremented value.Attribute
B
Base case
Bottleneck
Buffer overflow
Bytecode
C
Call stack
area function adds an entry to the call stack and removes it when its result returns to the caller.Callback
showMenu as a callback lets a button system call that function when the user clicks.Class
Loan class can define the member, book, due date, and renewal operation for each loan.Closure
Coercion
Compiler
Composition
Constant folding
60 * 60 * 24 with 86400 before the program starts.D
Dangling pointer
Data type
Declarative programming
Domain-specific language
Double free
free again on an already released allocation can create a double free and unpredictable behavior.Duck typing
area() method.Dynamic dispatch
shape.area() to the rectangle implementation when the receiver is a rectangle.Dynamic typing
E
Ecosystem
Encapsulation
Event handler
Event-driven programming
Expression
width * height supplies the value returned by a rectangular-area function.F
First-class function
map as the operation to apply to each collection item.Floating-point number
0.1 + 0.2 and the stored value of 0.3.Functional programming
G
Garbage collection
H
Heap
High-level language
Higher-order function
filter takes a predicate that decides which input values to retain.I
Immutability
Imperative programming
Implementation
Information hiding
Inheritance
Instruction set architecture
Interface
area() method while leaving each shape to calculate it differently.Interpreter
J
Just-in-time compilation
L
Lambda
lambda n: n * n supplies a squaring operation to a transformation pipeline.Lexical analysis
price * 3 is separated into a name, a multiplication operator, and a number.Linker
Logic error
a + b / 2 to average two values is a logic error because the division happens before addition.Logic programming
M
Machine code
Memory leak
Memory safety
Method
Multi-paradigm language
Mutation
O
Object
Overflow
Overriding
Ownership
P
Parameter
width receives 3 when the caller evaluates area(3, 4).Parsing
total = price * 3; belongs inside the assignment.Platform
Polymorphism
area() on circles and rectangles without a separate branch for each.Procedural programming
Programming paradigm
Pure function
Q
Query optimizer
R
Recursion
Reference
Reference counting
Regular expression
[0-9]{3}-[0-9]{4} matches a three-digit group followed by a hyphen and four digits.REPL
Runtime error
S
Scope
Semantics
Short-circuit evaluation
and.Side effect
SQL
Stack frame
Statement
total = price * 3 assigns the result of a multiplication to a variable.Static typing
Structured programming
Syntax
if condition and indentation for the controlled block.T
Token
Trace table
Turing completeness
Type inference
U
Use after free
V
Virtual machine
- 1Computational Thinking and Problem Solving
- 2Binary and Data Representation
- 3How Computers Work
- 4Searching, Sorting and Efficiency
- 5Programming Languages and Paradigms
- 6How the Internet Works
- 7Computing Ethics
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.