Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Language & library reference

Concept clusters →

Every named result in this course, grouped by chapter. Each one links back to where it's derived.

Before you start

Rule

One hexadecimal digit is exactly four bits

also known as: hex-to-binary by nibbles

Formal. Because 16 = 2^4, each base-16 digit corresponds to exactly one 4-bit group (a nibble). A binary number converts to hexadecimal by grouping its bits in fours from bit 0 and replacing each group by its hex digit, and back by expanding each hex digit to four bits.

In plain English. Hex is shorthand for binary. Split the bits into groups of four starting from the right, and write one hex digit per group; no arithmetic on the whole number is needed.

Example. 1011 0110 → B 6 → 0xB6. 0x3F → 0011 1111.

Why this rule exists. It is why debuggers, compilers and memory dumps print values in hex: two hex digits are one byte, and every bit is still visible at a glance.

Voltage, current, and resistance

Rule

Ohm's law

also known as: V = IR, the resistor law

Formal. For a resistor, the voltage V across it, the current I through it, and its resistance R satisfy V = I R, so I = V/R and R = V/I. With V in volts and R in ohms (\Omega), I comes out in amperes. The law describes resistors (and wires) over their working range; parts such as LEDs and diodes do not obey it, because their current is not proportional to their voltage.

In plain English. Voltage is the push, resistance is how hard the part pushes back, and current is how much flows as a result. Double the push and the flow doubles; double the pushback and the flow halves.

Example. 5 V across a 1 kΩ resistor drives 5/1000 = 0.005 A, which is 5 mA.

Why this rule exists. Almost every number you choose in a small circuit (how big a resistor, how much current a pin will supply) comes from rearranging this one relationship.

Formula

Electrical power

also known as: P = VI, power dissipation, I squared R

Formal. The power delivered to a part with voltage V across it and current I through it is P = V I. For a resistor, substituting Ohm's law gives P = I^2 R = V^2/R. With volts and amperes, P is in watts. Energy is power multiplied by time: E = P t.

In plain English. Power is how fast a part turns electrical energy into something else, usually heat. More voltage or more current means more heat.

Example. A resistor dropping 3 V while carrying 20 mA turns 3 \times 0.020 = 0.06 W (60 mW) into heat, comfortably under a ¼ W rating.

Why this formula exists. It tells you whether a resistor, transistor, or regulator will overheat, and how long a battery will last.

Circuits on a breadboard

Rule

Kirchhoff's voltage law

also known as: KVL, the loop rule, voltages around a loop add to zero

Formal. Around any closed loop in a circuit, the sum of the voltage rises equals the sum of the voltage drops: \sum_{\text{loop}} V = 0 when rises are counted positive and drops negative. It holds because voltage is a difference in electric potential between two points, so walking a loop back to where you started returns you to the same potential.

In plain English. Every volt a battery or supply pushes into a loop gets used up by the parts in that loop, exactly, before you get back to where you started. Nothing is left over and nothing is missing.

Example. A 5 V supply feeds a red LED and a resistor in one loop. If the LED uses 2.0 V, the resistor must use the other 3.0 V.

Why this rule exists. It is how you find the voltage across a part you can't measure directly, and how you check a calculation: if the drops in a loop don't add up to the supply, one of your numbers is wrong.

Rule

Kirchhoff's current law

also known as: KCL, the junction rule, current in equals current out

Formal. At any junction (node) of a circuit, the total current flowing in equals the total current flowing out: \sum I_{\text{in}} = \sum I_{\text{out}}. It holds because charge is conserved and a wire junction cannot store it.

In plain English. Current behaves like water in pipes that never leak. However much flows into a meeting point of wires must flow out of it.

Example. 30 mA arrives at a junction that splits into two branches. If one branch carries 10 mA, the other carries 20 mA.

Why this rule exists. It tells you how current divides between parallel branches, and it is the check that catches a branch you forgot to include.

Formula

The voltage divider

also known as: divider rule, resistive divider, potential divider

Formal. Two resistors R_1 and R_2 in series across an input voltage V_{in}, with the output taken across R_2 and nothing connected to the output, give V_{out} = V_{in}\,\frac{R_2}{R_1 + R_2}. It follows from Ohm's law: the same current I = V_{in}/(R_1+R_2) flows through both resistors, and V_{out} = I R_2. Anything connected to the output draws current and lowers V_{out} below this value.

In plain English. Two resistors in a row split the input voltage in proportion to their sizes. The bigger the bottom resistor's share of the total, the bigger the share of the voltage you get out.

Example. V_{in} = 5 V, R_1 = 10 kΩ, R_2 = 20 kΩ gives V_{out} = 5 \times 20/30 \approx 3.33 V.

Why this formula exists. It is how a microcontroller reads a sensor whose resistance changes (a thermistor, a light sensor), and how a higher voltage is scaled down to a level an input pin can safely accept.

Components you will use every day

Formula

Electrical power

also known as: P = VI, power dissipation, I squared R

Formal. The power delivered to a part with voltage V across it and current I through it is P = V I. For a resistor, substituting Ohm's law gives P = I^2 R = V^2/R. With volts and amperes, P is in watts. Energy is power multiplied by time: E = P t.

In plain English. Power is how fast a part turns electrical energy into something else, usually heat. More voltage or more current means more heat.

Example. A resistor dropping 3 V while carrying 20 mA turns 3 \times 0.020 = 0.06 W (60 mW) into heat, comfortably under a ¼ W rating.

Why this formula exists. It tells you whether a resistor, transistor, or regulator will overheat, and how long a battery will last.

Formula

RC charging and the time constant

also known as: tau = RC, the 63% rule, five time constants

Formal. A capacitor C charging through a resistor R from a constant supply V_s follows v(t) = V_s\left(1 - e^{-t/\tau}\right), \qquad \tau = RC, and discharging from V_0 follows v(t) = V_0\,e^{-t/\tau}. With R in ohms and C in farads, \tau is in seconds. After one \tau the voltage has covered about 63% of the way to its final value; after 5\tau, more than 99%.

In plain English. A capacitor fills like a bucket under a tap that slows down as the bucket gets fuller. The time constant is how long it takes to get about two-thirds full; after five of them, call it full.

Example. R = 10 kΩ and C = 1 µF give \tau = 10 ms, so the capacitor is essentially charged after 50 ms.

Why this formula exists. It sets reset delays, switch-debounce filters, and how smooth a filtered PWM signal is.

Formula

The voltage divider

also known as: divider rule, resistive divider, potential divider

Formal. Two resistors R_1 and R_2 in series across an input voltage V_{in}, with the output taken across R_2 and nothing connected to the output, give V_{out} = V_{in}\,\frac{R_2}{R_1 + R_2}. It follows from Ohm's law: the same current I = V_{in}/(R_1+R_2) flows through both resistors, and V_{out} = I R_2. Anything connected to the output draws current and lowers V_{out} below this value.

In plain English. Two resistors in a row split the input voltage in proportion to their sizes. The bigger the bottom resistor's share of the total, the bigger the share of the voltage you get out.

Example. V_{in} = 5 V, R_1 = 10 kΩ, R_2 = 20 kΩ gives V_{out} = 5 \times 20/30 \approx 3.33 V.

Why this formula exists. It is how a microcontroller reads a sensor whose resistance changes (a thermistor, a light sensor), and how a higher voltage is scaled down to a level an input pin can safely accept.

Power rails and logic levels

Formula

Electrical power

also known as: P = VI, power dissipation, I squared R

Formal. The power delivered to a part with voltage V across it and current I through it is P = V I. For a resistor, substituting Ohm's law gives P = I^2 R = V^2/R. With volts and amperes, P is in watts. Energy is power multiplied by time: E = P t.

In plain English. Power is how fast a part turns electrical energy into something else, usually heat. More voltage or more current means more heat.

Example. A resistor dropping 3 V while carrying 20 mA turns 3 \times 0.020 = 0.06 W (60 mW) into heat, comfortably under a ¼ W rating.

Why this formula exists. It tells you whether a resistor, transistor, or regulator will overheat, and how long a battery will last.

Signals: analog, digital, and PWM

Formula

ADC code to voltage

also known as: ADC resolution, step size, LSB size

Formal. An n-bit ADC with reference voltage V_{ref} reports a code from 0 to 2^n - 1. Taking the step size as V_{ref}/2^n, an input voltage V gives the code \lfloor V \cdot 2^n / V_{ref} \rfloor, and a code k corresponds to V \approx k \cdot V_{ref}/2^n. Some datasheets and libraries divide by 2^n - 1 instead; the difference is one step.

In plain English. The converter chops its voltage range into a fixed number of equal steps and reports which step the input is on. More bits means more, smaller steps.

Example. A 12-bit ADC (4096 steps) with a 3.3 V reference has steps of about 0.8 mV, so a reading of 2048 means about 1.65 V.

Why this formula exists. It turns the number your firmware reads back into the voltage, and then the temperature, light level, or position, that you actually care about.

Formula

PWM duty cycle and average voltage

also known as: duty cycle, average of a PWM signal

Formal. A PWM signal that is HIGH (V_H) for time t_{on} in every period T = 1/f and LOW (0 V) otherwise has duty cycle D = t_{on}/T and average voltage V_{avg} = D\,V_H. The signal itself is never at V_{avg}: it only ever switches between 0 and V_H.

In plain English. Switching something fully on and fully off very fast acts, on average, like a lower voltage. The bigger the fraction of time it is on, the higher that average.

Example. A 3.3 V pin at 25% duty cycle averages 0.25 \times 3.3 \approx 0.83 V.

Why this formula exists. It is how a pin with only HIGH and LOW dims an LED or slows a motor, and it is the number a filter turns into a real steady voltage.

Formula

RC charging and the time constant

also known as: tau = RC, the 63% rule, five time constants

Formal. A capacitor C charging through a resistor R from a constant supply V_s follows v(t) = V_s\left(1 - e^{-t/\tau}\right), \qquad \tau = RC, and discharging from V_0 follows v(t) = V_0\,e^{-t/\tau}. With R in ohms and C in farads, \tau is in seconds. After one \tau the voltage has covered about 63% of the way to its final value; after 5\tau, more than 99%.

In plain English. A capacitor fills like a bucket under a tap that slows down as the bucket gets fuller. The time constant is how long it takes to get about two-thirds full; after five of them, call it full.

Example. R = 10 kΩ and C = 1 µF give \tau = 10 ms, so the capacitor is essentially charged after 50 ms.

Why this formula exists. It sets reset delays, switch-debounce filters, and how smooth a filtered PWM signal is.

Just enough C++ for firmware

Rule

Integer arithmetic rules

also known as: integer division drops the fraction, remainder sign, unsigned wraparound

Formal. When both sides of / are integers, the result is an integer: the fraction is dropped, rounding toward zero, so 7 / 2 is 3 and -7 / 2 is -3. % gives the remainder with the sign of the left side: -7 % 2 is -1. If either side is floating-point, the other is converted first and the division is floating-point. Unsigned arithmetic wraps modulo 2ⁿ, so 0u - 1 is the largest unsigned int. Signed overflow is undefined behavior.

In plain English. Two whole numbers divide to a whole number: the fraction is dropped, never rounded. The remainder keeps the sign of the number being divided. Put a decimal on either side and you get a decimal answer. Unsigned numbers roll over like an odometer, but going past the biggest signed number is a bug with no promised result.

Example. static_cast<double>(total) / count converts before dividing, so an average of 7 and 2 keeps its .5.

Why this rule exists. Integer division silently drops fractions, the most common source of wrong averages and percentages in C++.

Rule

Control-flow rules

also known as: short-circuiting, = versus ==, switch fallthrough, off-by-one

Formal. && and || short-circuit: the right side runs only if the left side doesn't already decide the answer. = assigns and == compares, so if (x = 5) stores 5 and the test is always true. Arithmetic binds tighter than comparison, comparison tighter than &&, and && tighter than ||, so a || b && c means a || (b && c). A switch jumps to the matching case and then falls through into the cases below it until a break. [[fallthrough]]; marks a fallthrough you meant, and only silences the warning. for (int i = 0; i < n; ++i) visits 0 to n - 1; writing <= runs one time too many.

In plain English. C++ stops checking as soon as it knows the answer, so put a guard like count > 0 first. One = stores, two compare. && groups before ||, and parentheses make it clear. A switch keeps running into the next case until it meets break. A counting loop over n things stops before n.

Example. count > 0 && total / count > 90 never divides by zero: when count is 0 the left side is false and the division is skipped.

Why this rule exists. Each of these compiles, runs, and gives a wrong answer, which makes them some of the most common bugs in beginner and professional code alike.

Rule

Scope and lifetime rules

also known as: block scope, automatic lifetime, static locals, globals, namespaces

Formal. Scope is where in the source a name can be used; lifetime is when the object behind it exists while the program runs. A local's name works from its declaration to the closing brace of its block, including nested blocks. A local is created when execution reaches it and destroyed when execution leaves its block, so every call starts fresh. A static local is initialized once, the first time execution reaches it, and keeps its value until the program ends; its scope is unchanged. A global's name works in every function below it, and it lives from before main starts until the program ends. A name inside a namespace is used from outside as family::name, and the same name in two namespaces never collides. An unnamed namespace, namespace { ... }, keeps its contents private to one .cpp file.

In plain English. Scope is where you can write a name; lifetime is when the variable is alive. A plain local lives on its call's plate and is cleared at its }. The word static moves it to a permanent shelf, but its name still works only inside its function. A global is on that shelf too, and every function can change it. Namespaces give names a family name, and a namespace with no name keeps things private to one file.

Example. static int ticket = 100; inside next_ticket(), with ++ticket; return ticket;, gives 101, 102, 103 on three calls. Without static it gives 101 every time.

Why this rule exists. Knowing where a name works and how long its variable lives explains "not declared in this scope" errors, functions that forget, and globals that change behind your back.

Your first firmware

Formula

The voltage divider

also known as: divider rule, resistive divider, potential divider

Formal. Two resistors R_1 and R_2 in series across an input voltage V_{in}, with the output taken across R_2 and nothing connected to the output, give V_{out} = V_{in}\,\frac{R_2}{R_1 + R_2}. It follows from Ohm's law: the same current I = V_{in}/(R_1+R_2) flows through both resistors, and V_{out} = I R_2. Anything connected to the output draws current and lowers V_{out} below this value.

In plain English. Two resistors in a row split the input voltage in proportion to their sizes. The bigger the bottom resistor's share of the total, the bigger the share of the voltage you get out.

Example. V_{in} = 5 V, R_1 = 10 kΩ, R_2 = 20 kΩ gives V_{out} = 5 \times 20/30 \approx 3.33 V.

Why this formula exists. It is how a microcontroller reads a sensor whose resistance changes (a thermistor, a light sensor), and how a higher voltage is scaled down to a level an input pin can safely accept.

Formula

ADC code to voltage

also known as: ADC resolution, step size, LSB size

Formal. An n-bit ADC with reference voltage V_{ref} reports a code from 0 to 2^n - 1. Taking the step size as V_{ref}/2^n, an input voltage V gives the code \lfloor V \cdot 2^n / V_{ref} \rfloor, and a code k corresponds to V \approx k \cdot V_{ref}/2^n. Some datasheets and libraries divide by 2^n - 1 instead; the difference is one step.

In plain English. The converter chops its voltage range into a fixed number of equal steps and reports which step the input is on. More bits means more, smaller steps.

Example. A 12-bit ADC (4096 steps) with a 3.3 V reference has steps of about 0.8 mV, so a reading of 2048 means about 1.65 V.

Why this formula exists. It turns the number your firmware reads back into the voltage, and then the temperature, light level, or position, that you actually care about.

Formula

PWM duty cycle and average voltage

also known as: duty cycle, average of a PWM signal

Formal. A PWM signal that is HIGH (V_H) for time t_{on} in every period T = 1/f and LOW (0 V) otherwise has duty cycle D = t_{on}/T and average voltage V_{avg} = D\,V_H. The signal itself is never at V_{avg}: it only ever switches between 0 and V_H.

In plain English. Switching something fully on and fully off very fast acts, on average, like a lower voltage. The bigger the fraction of time it is on, the higher that average.

Example. A 3.3 V pin at 25% duty cycle averages 0.25 \times 3.3 \approx 0.83 V.

Why this formula exists. It is how a pin with only HIGH and LOW dims an LED or slows a motor, and it is the number a filter turns into a real steady voltage.

C on the metal

Rule

Where array element i lives

also known as: base plus index times size, address arithmetic

Formal. For an array of type T whose first element is at address base, element i is at base + i × sizeof(T). In C++, for T* p, the expression p + i means exactly that address, and p[i] is *(p + i).

In plain English. Elements sit side by side, each taking the same number of bytes, so finding element i is a multiplication and an addition — no searching.

Example. An int array starting at 0x1000: element 3 is at 0x1000 + 3 × 4 = 0x100C. A std::int16_t array at the same place: element 3 is at 0x1000 + 3 × 2 = 0x1006.

Why this rule exists. It is why array indexing is fast, why indexes start at 0, and why the addressing modes in Chapter 7 have a "scale" part.

Rule

How a struct is laid out in memory

also known as: field offsets and padding, sizeof and alignof of a struct

Formal. For a C/C++ struct (standard layout, no packing directives) with fields f₁ … fₖ in declaration order:

  1. offset(f₁) = 0;
  2. offset(fᵢ) = the smallest multiple of alignof(fᵢ) that is ≥ offset(fᵢ₋₁)
    • sizeof(fᵢ₋₁);
  3. alignof(struct) = the largest alignof(fᵢ);
  4. sizeof(struct) = offset(fₖ) + sizeof(fₖ) rounded up to a multiple of alignof(struct).

The bytes skipped in step 2 and step 4 are padding.

In plain English. Place the fields in order, sliding each one forward until it starts on a multiple of its own alignment. Then round the total up so that the next struct in an array starts properly aligned too.

Example. struct S { char c; int i; char d; };: c at 0, i at 4 (after 3 padding bytes), d at 8, then 3 bytes of tail padding: sizeof 12, alignof 4. Reordered as { int i; char c; char d; }: offsets 0, 4, 5, sizeof 8.

Why this rule exists. Aligned values can be loaded in one access and some instructions require it; the ABI fixes this rule so that separately compiled C++ and assembly agree on every field's offset.

Rule

The as-if rule

also known as: observable behaviour, what the optimizer must preserve

Formal. A C++ implementation may transform a program in any way, provided that, for every program execution without undefined behaviour, the observable behaviour is that of the abstract machine: the sequence of accesses through volatile glvalues, the data written to files by termination, and the prompting and input/output of interactive devices. For an execution that has undefined behaviour, no requirement applies.

In plain English. The compiler may rewrite, reorder, merge or delete your code however it likes, as long as what the outside world can see — the output, the files, any volatile accesses — comes out the same. If the program does something undefined, the compiler owes it nothing at all.

Example. int f() { int s = 0; for (int i = 1; i <= 100; ++i) s += i; return s; } compiles at -O2 to the equivalent of return 5050;. The loop is gone, but nothing observable changed: f still returns 5050.

Why this rule exists. Without it, every optimization would need its own permission in the language standard. One rule — keep the visible behaviour — licenses constant folding, inlining, dead-code elimination and the rest, and explains why a benchmark whose result is unused may measure nothing.

Modern C++ without the bloat

Rule

Scope and lifetime rules

also known as: block scope, automatic lifetime, static locals, globals, namespaces

Formal. Scope is where in the source a name can be used; lifetime is when the object behind it exists while the program runs. A local's name works from its declaration to the closing brace of its block, including nested blocks. A local is created when execution reaches it and destroyed when execution leaves its block, so every call starts fresh. A static local is initialized once, the first time execution reaches it, and keeps its value until the program ends; its scope is unchanged. A global's name works in every function below it, and it lives from before main starts until the program ends. A name inside a namespace is used from outside as family::name, and the same name in two namespaces never collides. An unnamed namespace, namespace { ... }, keeps its contents private to one .cpp file.

In plain English. Scope is where you can write a name; lifetime is when the variable is alive. A plain local lives on its call's plate and is cleared at its }. The word static moves it to a permanent shelf, but its name still works only inside its function. A global is on that shelf too, and every function can change it. Namespaces give names a family name, and a namespace with no name keeps things private to one file.

Example. static int ticket = 100; inside next_ticket(), with ++ticket; return ticket;, gives 101, 102, 103 on three calls. Without static it gives 101 every time.

Why this rule exists. Knowing where a name works and how long its variable lives explains "not declared in this scope" errors, functions that forget, and globals that change behind your back.

Sensors

Formula

The voltage divider

also known as: divider rule, resistive divider, potential divider

Formal. Two resistors R_1 and R_2 in series across an input voltage V_{in}, with the output taken across R_2 and nothing connected to the output, give V_{out} = V_{in}\,\frac{R_2}{R_1 + R_2}. It follows from Ohm's law: the same current I = V_{in}/(R_1+R_2) flows through both resistors, and V_{out} = I R_2. Anything connected to the output draws current and lowers V_{out} below this value.

In plain English. Two resistors in a row split the input voltage in proportion to their sizes. The bigger the bottom resistor's share of the total, the bigger the share of the voltage you get out.

Example. V_{in} = 5 V, R_1 = 10 kΩ, R_2 = 20 kΩ gives V_{out} = 5 \times 20/30 \approx 3.33 V.

Why this formula exists. It is how a microcontroller reads a sensor whose resistance changes (a thermistor, a light sensor), and how a higher voltage is scaled down to a level an input pin can safely accept.

Actuators and human interfaces

Formula

PWM duty cycle and average voltage

also known as: duty cycle, average of a PWM signal

Formal. A PWM signal that is HIGH (V_H) for time t_{on} in every period T = 1/f and LOW (0 V) otherwise has duty cycle D = t_{on}/T and average voltage V_{avg} = D\,V_H. The signal itself is never at V_{avg}: it only ever switches between 0 and V_H.

In plain English. Switching something fully on and fully off very fast acts, on average, like a lower voltage. The bigger the fraction of time it is on, the higher that average.

Example. A 3.3 V pin at 25% duty cycle averages 0.25 \times 3.3 \approx 0.83 V.

Why this formula exists. It is how a pin with only HIGH and LOW dims an LED or slows a motor, and it is the number a filter turns into a real steady voltage.

Formula

Electrical power

also known as: P = VI, power dissipation, I squared R

Formal. The power delivered to a part with voltage V across it and current I through it is P = V I. For a resistor, substituting Ohm's law gives P = I^2 R = V^2/R. With volts and amperes, P is in watts. Energy is power multiplied by time: E = P t.

In plain English. Power is how fast a part turns electrical energy into something else, usually heat. More voltage or more current means more heat.

Example. A resistor dropping 3 V while carrying 20 mA turns 3 \times 0.020 = 0.06 W (60 mW) into heat, comfortably under a ¼ W rating.

Why this formula exists. It tells you whether a resistor, transistor, or regulator will overheat, and how long a battery will last.

Power and battery life

Formula

Electrical power

also known as: P = VI, power dissipation, I squared R

Formal. The power delivered to a part with voltage V across it and current I through it is P = V I. For a resistor, substituting Ohm's law gives P = I^2 R = V^2/R. With volts and amperes, P is in watts. Energy is power multiplied by time: E = P t.

In plain English. Power is how fast a part turns electrical energy into something else, usually heat. More voltage or more current means more heat.

Example. A resistor dropping 3 V while carrying 20 mA turns 3 \times 0.020 = 0.06 W (60 mW) into heat, comfortably under a ¼ W rating.

Why this formula exists. It tells you whether a resistor, transistor, or regulator will overheat, and how long a battery will last.