01 Webprog Lab 1 - JS Fundamentals

On this page (33 sections)

JavaScript Fundamentals: Practice 01 Notes

Course materials Console Output

Declaring variables#

JavaScript has three ways to declare a variable: let, const, and the older var.

  • let: declares a variable that can be reassigned later. Use this by default for values that change.
  • const: declares a variable that cannot be reassigned. Use this by default for values that stay the same (this does not make objects/arrays immutable; their contents can still change).
  • var: the legacy way to declare variables. Avoid it in new code; it has confusing scoping rules (function-scoped instead of block-scoped) that let/const fix.
let age = 25;          // can be reassigned
const name = "Anna";   // cannot be reassigned
age = 26;               // OK
// name = "Bela";      // Error: Assignment to constant variable.

Rule of thumb: start with const; switch to let only if you actually need to reassign the variable.

Printing to the console#

console.log() writes output to the browser’s or Node’s developer console. It’s the primary way to inspect values while learning and debugging.

const name = "Anna";
console.log("Hello, " + name + "!"); // Hello, Anna!

console.log accepts multiple comma-separated arguments, which it prints space-separated, which is handy for quick debugging without building a single string:

console.log("Hello,", name, "!"); // Hello, Anna !

2. String Building and Template Literals#

Generating HTML strings from data is a recurring task in these exercises, so comfortable string building is essential.

Concatenation with +#

The + operator joins strings together. Non-string values are automatically converted to strings.

const name = "Anna";
const greeting = "Hello, " + name + "!";

This works but becomes hard to read once you mix in many variables and quotes (especially HTML tags with their own quoted attributes).

Template literals (preferred)#

Template literals use backticks (`) instead of quotes and let you embed expressions directly with ${...}. This is the modern, readable way to build strings, especially HTML strings.

const name = "Anna";
const greeting = `Hello, ${name}!`;
const heading = `<h1>Hello, ${name}!</h1>`;

Why this matters for HTML generation: template literals can also span multiple lines without special characters, which makes building multi-line HTML (like table rows) far cleaner than string concatenation:

const row = `
  <tr>
    <td>${id}</td>
    <td>${name}</td>
  </tr>
`;

Building HTML from data: the general pattern#

The recurring pattern in these exercises is: take structured data (a value, an array, an array of objects) → transform each piece into an HTML fragment → combine the fragments into one string.

For a single value, this is just a template literal. For a list of values, you typically:

  1. Transform each item into an HTML fragment (often with .map(), covered in section 5).
  2. Join the fragments into one string (with .join(""), since array .map() returns an array).
  3. Wrap the joined string in its container tag (<ul>, <table>, <div>, etc.).
const names = ["Anna", "Bela", "Cecil"];

const listItems = names
  .map(name => `<li>${name}</li>`)
  .join("");

const list = `<ul>${listItems}</ul>`;
console.log(list);
// <ul><li>Anna</li><li>Bela</li><li>Cecil</li></ul>

The same pattern extends naturally to tables: each row is generated from an object’s fields, and rows are joined inside a <table> wrapper.


3. Control Flow#

Conditionals: if / else#

if runs a block only when a condition is truthy; else provides a fallback.

const isLoggedIn = true;

let html;
if (isLoggedIn) {
  html = `<div>Welcome back! <a href="/logout">Logout</a></div>`;
} else {
  html = `<div><a href="/login">Login</a></div>`;
}

Why this matters: many exercises ask you to generate different HTML depending on a boolean flag (like isLoggedIn). The pattern is always: check the condition, branch, produce the appropriate string in each branch.

Comparison and logical operators#

  • === / !==: strict equality/inequality (compares value and type; always prefer these over ==/!=, which perform type coercion and can produce surprising results).
  • <, >, <=, >=: numeric/string comparisons.
  • && (AND), || (OR), ! (NOT): combine boolean conditions.
  • %: the modulo (remainder) operator, essential for even/odd checks and divisibility tests.
const n = 7;
console.log(n % 2 === 0); // false → n is odd
console.log(n % 2 !== 0); // true  → n is odd

Loops#

for loop: used when you know how many times to iterate, or need the index (e.g., iterating an array by position, or looping from 1 to n for a factorial).

for (let i = 1; i <= 5; i++) {
  console.log(i);
}

while loop: used when the number of iterations isn’t known in advance and depends on a condition becoming false (e.g., the Euclidean algorithm for GCD, where you loop “while remainder > 0”).

let a = 48, b = 18;
while (b > 0) {
  const remainder = a % b;
  a = b;
  b = remainder;
}
console.log(a); // 6 (the GCD)

for...of loop: the cleanest way to iterate over array values directly, without manual indexing.

const numbers = [1, 2, 3, 4];
for (const num of numbers) {
  console.log(num);
}

Choosing between them:

  • Need an index or a numeric range? → for
  • Need to loop “until a condition changes,” with an unknown iteration count? → while
  • Just need each value of an array/iterable, no index needed? → for...of

4. Functions#

Why functions#

A function packages reusable logic behind a name, taking inputs (parameters) and producing an output (return value). Any task described as “write a function that calculates/returns/decides…” should be implemented as a function with a clear input and output, not just inline code that prints an answer.

Function declaration#

function factorial(n) {
  let result = 1;
  for (let i = 2; i <= n; i++) {
    result *= i;
  }
  return result;
}

console.log(factorial(5)); // 120

Arrow functions#

Arrow functions are a shorter syntax, especially useful for small functions passed to array methods (see section 5). They don’t have their own this, which is irrelevant for these exercises but worth knowing as the key difference from regular functions.

const square = x => x * x;              // single param, single expression: no parens/braces/return needed
const add = (a, b) => a + b;             // multiple params need parentheses
const isEven = n => n % 2 === 0;

Parameters, return values, and recursion#

  • A function can take zero, one, or many parameters.
  • return exits the function and sends a value back to the caller. A function with no return implicitly returns undefined.
  • Some problems (like factorial) can also be expressed recursively: a function calling itself with a smaller input until it reaches a base case.
// Recursive factorial: an alternative to the loop-based version above
function factorial(n) {
  if (n <= 1) return 1;        // base case: stops the recursion
  return n * factorial(n - 1); // recursive case: smaller subproblem
}

Both the iterative (loop-based) and recursive versions are valid; the iterative version is usually clearer for beginners and avoids call-stack concerns for large inputs.

Multiple return values via helper functions#

Some problems (like GCD/LCM) are naturally two-step: compute something, then use it. It’s good practice to write small, single-purpose functions (gcd, isPrime, isEven) and combine them, rather than one large function doing everything.


5. Arrays and Iteration#

Arrays are the primary data structure for “a series of numbers,” “an array of names,” or “a catalogue” of items.

Creating and accessing arrays#

const numbers = [3, -1, 7, 0, -5];
console.log(numbers[0]);        // 3 (first element, index 0)
console.log(numbers.length);    // 5 (number of elements)
console.log(numbers[numbers.length - 1]); // -5 (last element)

Basic iteration with for / for...of#

Any task phrased as “find,” “count,” or “check every element” starts from iterating the array and inspecting each value.

const numbers = [3, -1, 7, 0, -5];

// Find: stop as soon as you find a match
let firstNegative = undefined;
for (const n of numbers) {
  if (n < 0) {
    firstNegative = n;
    break; // "break" exits the loop early once found
  }
}

// Count: accumulate a running total
let evenCount = 0;
for (const n of numbers) {
  if (n % 2 === 0) evenCount++;
}

break exits a loop immediately (useful once you’ve found what you’re looking for and don’t need to keep checking). continue skips to the next iteration without exiting the loop entirely.

Array methods (higher-level alternatives to manual loops)#

Modern JavaScript provides array methods that express common patterns without writing the loop by hand. These are generally preferred once you’re comfortable with plain loops, because they state intent directly.

MethodPurposeReturns
.forEach(fn)Run fn for each element (side effects, e.g. logging)undefined
.map(fn)Transform each element into something newa new array, same length
.filter(fn)Keep only elements matching a conditiona new array, same or shorter length
.find(fn)Return the first element matching a conditiona single element, or undefined
.some(fn)Check if at least one element matchesa boolean
.every(fn)Check if all elements matcha boolean
.reduce(fn, initial)Combine all elements into a single value (sum, max, etc.)a single accumulated value
const numbers = [3, -1, 7, 0, -5, 8];

const doubled = numbers.map(n => n * 2);                 // transform
const positives = numbers.filter(n => n > 0);             // select matching
const firstNegative = numbers.find(n => n < 0);            // -1
const hasNegative = numbers.some(n => n < 0);               // true
const allEven = numbers.every(n => n % 2 === 0);            // false
const sum = numbers.reduce((total, n) => total + n, 0);      // 12

Mapping tasks to methods:

  • “Find a negative number” → .find()
  • “Count the even numbers” → .filter(...).length, or .reduce()
  • “Put the prime numbers in a new array” → .filter()
  • “Decide whether all elements are even” → .every()
  • “Decide whether there is a value over 40” → .some()
  • “What is the highest value” → .reduce() (tracking a running max), or Math.max(...array)
  • “Select values when it was freezing” → .filter()
  • “Put text at the end of each value” → .map()

Finding min/max, sums, and averages#

Math.max() and Math.min() take individual arguments, not an array, so use the spread operator (...) to expand an array into arguments.

const temps = [22, 25, 19, 27, 24];
const highest = Math.max(...temps);
const lowest = Math.min(...temps);
const sum = temps.reduce((total, t) => total + t, 0);
const average = sum / temps.length;

To find which day (index) had a particular value, not just the value itself, iterate with an index (for loop, or .forEach((value, index) => ...), or .findIndex()):

const temps = [22, 25, 19, 27, 24];
const coldestIndex = temps.indexOf(Math.min(...temps));
console.log(`Day ${coldestIndex + 1} was coldest`); // +1 if days are 1-indexed for humans

Comparing neighboring elements#

Tasks like “differs from both neighbors” or “two consecutive days changed by 3 degrees” require looking at an element and its neighbor(s), so you iterate by index and access array[i], array[i - 1], array[i + 1], being careful not to go out of bounds at the start/end of the array.

const temps = [22, 25, 19, 27, 24];

for (let i = 1; i < temps.length - 1; i++) { // skip first and last: no two neighbors
  const diffPrev = Math.abs(temps[i] - temps[i - 1]);
  const diffNext = Math.abs(temps[i] - temps[i + 1]);
  console.log(`Day ${i}: diff from prev=${diffPrev}, diff from next=${diffNext}`);
}

Tracking the longest streak#

“How many days did the longest period consist of when water was over 25 degrees” is a running streak problem: keep a counter that increments while the condition holds and resets when it breaks, tracking the best value seen.

const temps = [24, 26, 27, 28, 23, 26, 26, 26, 20];
let currentStreak = 0;
let longestStreak = 0;

for (const t of temps) {
  if (t > 25) {
    currentStreak++;
    longestStreak = Math.max(longestStreak, currentStreak);
  } else {
    currentStreak = 0; // condition broke: reset
  }
}
console.log(longestStreak); // 4

Two-dimensional arrays (matrices)#

A matrix is represented as an array of arrays, where each inner array is a row. To check something about every element, nest a loop (or nest .every()/.flat()):

const matrix = [
  [2, 4, 6],
  [8, 10, 12],
  [1, 4, 6],
];

// Using nested loops
let allEven = true;
for (const row of matrix) {
  for (const value of row) {
    if (value % 2 !== 0) allEven = false;
  }
}

// Using .every() with .flat() (flattens the matrix into one array first)
const allEvenConcise = matrix.flat().every(n => n % 2 === 0);

6. Numeric Algorithms#

A few exercises implement specific numeric algorithms. The general approach: translate the pseudo-code line by line into JavaScript, using the control-flow constructs from section 3.

Divisibility and primality#

A number n is divisible by d when n % d === 0. A number is prime when it’s greater than 1 and has no divisors other than 1 and itself, checked by testing divisibility from 2 up to n:

function isPrime(n) {
  if (n < 2) return false;
  for (let i = 2; i < n; i++) {
    if (n % i === 0) return false; // found a divisor: not prime
  }
  return true;
}

Greatest common divisor (Euclidean algorithm) and least common multiple#

These follow directly from translating the provided pseudo-code: a while loop that repeatedly reduces the problem (GCD via remainders, LCM via repeated addition) until the loop’s exit condition is met. The key translation notes:

  • Pseudo-code mod → JavaScript %.
  • Pseudo-code swap(a, b) → use a temporary variable, or destructuring: [a, b] = [b, a].
  • Pseudo-code While condition ... End of while → JavaScript while (condition) { ... }.
function greatestCommonDivisor(a, b) {
  if (a < b) [a, b] = [b, a]; // ensure a >= b
  let residual = a % b;
  while (residual > 0) {
    a = b;
    b = residual;
    residual = a % b;
  }
  return b;
}

The least common multiple follows the same translation approach from its own pseudo-code (repeated addition until both running totals are equal).


7. Working with Objects and Arrays of Objects#

Object literals#

An object groups related named fields together: the natural representation for “student data” (Neptun ID, name, birth date) or “movie data” (title, length, year, directors, actors).

const student = {
  neptunId: "ABC123",
  name: "Anna Kovács",
  birthDate: "2001-05-12",
};

console.log(student.name);       // dot notation
console.log(student["name"]);    // bracket notation (useful when the key is a variable)

Arrays of objects#

A “catalogue” or “list of student data” is an array where each element is an object with the same shape. This is the standard structure for tabular data.

const movies = [
  { title: "Inception", length: 148, year: 2010, directors: ["Christopher Nolan"], actors: ["Leonardo DiCaprio"] },
  { title: "Sin City", length: 124, year: 2005, directors: ["Robert Rodriguez", "Frank Miller"], actors: ["Bruce Willis"] },
];

Filtering and transforming arrays of objects#

The array methods from section 5 work the same way on objects; you just access fields inside the callback instead of comparing plain values.

// "Movies with several directors (not one)"
const multiDirectorMovies = movies.filter(movie => movie.directors.length > 1);

// "The longest movie": reduce to track the running maximum
const longestMovie = movies.reduce((longest, movie) =>
  movie.length > longest.length ? movie : longest
);

// Listing movies to the console: combine template literals with iteration
movies.forEach(movie => {
  console.log(`${movie.title} (${movie.year}), ${movie.length} min`);
});

Generating an HTML table from an array of objects#

This combines everything above: iterate the array, generate a <tr> per object using its fields, join the rows, and wrap in <table>.

const students = [
  { neptunId: "ABC123", name: "Anna Kovács", birthDate: "2001-05-12" },
  { neptunId: "XYZ789", name: "Béla Nagy", birthDate: "2000-11-03" },
];

const rows = students
  .map(s => `
    <tr>
      <td>${s.neptunId}</td>
      <td>${s.name}</td>
      <td>${s.birthDate}</td>
    </tr>
  `)
  .join("");

const table = `
  <table>
    <thead>
      <tr><th>Neptun ID</th><th>Name</th><th>Birth date</th></tr>
    </thead>
    <tbody>${rows}</tbody>
  </table>
`;

console.log(table);

This is the same three-step pattern from section 2 (transform each item → join → wrap), just applied to objects instead of plain strings.


Topic Summary and Progression#

The topics build on each other in this order:

  1. Variables & output: the basic vocabulary for storing and displaying values.
  2. Strings & template literals: combining variables into readable text and HTML.
  3. Control flow: conditionals and loops, needed to make decisions and repeat work.
  4. Functions: packaging logic for reuse, with clear inputs and outputs.
  5. Arrays & iteration: the core data structure for “a series of…” problems, plus the standard methods (map, filter, find, some, every, reduce) for common patterns.
  6. Numeric algorithms: applying control flow and functions to translate pseudo-code (primality, GCD, LCM).
  7. Objects & arrays of objects: modeling structured, multi-field data (students, movies) and generating HTML tables from it.

Mastering steps 1–5 covers the large majority of these exercises; steps 6–7 are applications of the same fundamentals to more specific problem shapes.