Final Review

Final Exam Review

Preparing for the Final

CSCI 4513

Week 16, Lecture 29

Full Semester Coverage

Norse Mythology Connection

Ragnarök: The Final Test

Ragnarök tests everything the gods have learned, just as the final exam comprehensively evaluates all the skills you've acquired this semester.

The Tale:

Ragnarök—the twilight of the gods—is the ultimate test. It's not a single challenge but a comprehensive battle where everything the Aesir have learned throughout their existence comes into play. Thor's strength gained from countless battles, Odin's wisdom gathered from his sacrifices, Tyr's courage proven through his trials—all are tested simultaneously.

The gods knew Ragnarök was coming. The Norns prophesied it. Every lesson learned, every skill mastered, every alliance forged was preparation for this moment. They couldn't avoid it—they could only prepare. And though the battle would be fierce, they faced it knowing they had trained for exactly this.

Your final exam is your Ragnarök—a comprehensive test of everything you've learned. HTML fundamentals, JavaScript mastery, React components, backend APIs, database queries, authentication flows. All the "battles" you've fought this semester prepared you for this moment.

The Preparation

The Gods' Training

  • Thor practiced combat daily
  • Odin gathered wisdom constantly
  • Heimdall honed his vigilance
  • Each god mastered their domain
  • All skills would be tested

Your Semester

  • Practiced HTML/CSS fundamentals
  • Mastered JavaScript and OOP
  • Built React applications
  • Created backend with Node/Express
  • Everything comes together now
💡 Key Insight: Like the gods at Ragnarök, you've been preparing all semester. Trust your training.

HTML & CSS Review

Topics Covered:

  • Lecture 2: HTML Forms & Validation
  • Lecture 3: CSS Grid Layout
  • Lecture 16: CSS Animation
  • Lecture 17: Responsive Design

HTML Forms Quick Review

Key Form Elements

<form action="/submit" method="POST">
  <label for="email">Email:</label>
  <input type="email" id="email" required>

  <label for="age">Age:</label>
  <input type="number" id="age"
         min="18" max="120">
</form>

Validation Attributes

  • required
  • minlength, maxlength
  • min, max
  • pattern (regex)
  • type (email, number, date)

CSS Grid Quick Review

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: 20px;
}

.item {
  grid-column: span 2;    /* Span 2 columns */
  grid-row: 1 / 3;        /* From line 1 to 3 */
}

/* Responsive grid */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

CSS Animation Review

/* Define animation */
@keyframes slideIn {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Apply animation */
.element {
  animation: slideIn 0.5s ease-out forwards;
}

/* Transitions */
.button {
  transition: all 0.3s ease;
}

Responsive Design Review

/* Mobile-first approach */
.container {
  width: 100%;
  padding: 10px;
}

/* Tablet */
@media (min-width: 768px) {
  .container {
    width: 750px;
    margin: 0 auto;
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .container {
    width: 960px;
  }
}
đź’ˇ Remember: Mobile-first! Start with mobile styles, then add media queries for larger screens.

JavaScript Fundamentals Review

Topics Covered:

  • Lecture 4: Objects & Constructors
  • Lecture 5: Factory Functions & Closures
  • Lecture 6: ES6 Classes
  • Lecture 7: ES6 Modules & npm
  • Lecture 10: Async/Await & APIs

Constructor Functions

function Player(name, level) {
  this.name = name;
  this.level = level;
}

// Add methods to prototype
Player.prototype.greet = function() {
  return `Hello, I'm ${this.name}!`;
};

const player1 = new Player('Thor', 100);
console.log(player1.greet()); // "Hello, I'm Thor!"
⚠️ Remember: Must use new keyword! Methods go on prototype, not in constructor.

Factory Functions & Closures

function createPlayer(name, level) {
  let health = 100;  // Private variable

  return {
    name,  // Public property
    level,

    getHealth() {  // Public method accessing private var
      return health;
    },

    takeDamage(damage) {
      health -= damage;
    }
  };
}

const player = createPlayer('Odin', 50);
console.log(player.health);  // undefined (private!)
console.log(player.getHealth());  // 100

ES6 Classes

class Player {
  #health = 100;  // Private field

  constructor(name, level) {
    this.name = name;
    this.level = level;
  }

  greet() {
    return `I'm ${this.name}, level ${this.level}`;
  }

  get health() {
    return this.#health;
  }

  static compare(p1, p2) {
    return p1.level - p2.level;
  }
}

class Warrior extends Player {
  constructor(name, level, weapon) {
    super(name, level);
    this.weapon = weapon;
  }
}

ES6 Modules

Exporting

// Named exports
export function add(a, b) {
  return a + b;
}

export const PI = 3.14;

// Default export
export default class Game {
  // ...
}

Importing

// Named imports
import { add, PI } from './math.js';

// Default import
import Game from './game.js';

// Import all
import * as math from './math.js';

Async/Await & Fetch

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
}

// Using the function
getData().then(data => console.log(data));
⚠️ Remember: await only works inside async functions!

React Review

Topics Covered:

  • Lecture 18: React Environment & JSX
  • Lecture 19: Components, Props, State
  • Lecture 20: useEffect, Routing
  • Lecture 21: Context, Custom Hooks

React Components & Props

// Functional Component
function Greeting({ name, age }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>You are {age} years old.</p>
    </div>
  );
}

// Using the component
<Greeting name="Thor" age={1500} />
đź’ˇ Remember: Props are read-only! Pass data down, not up.

useState Hook

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}
⚠️ Remember: Never mutate state directly! Always use setter function.

useEffect Hook

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Fetch user data when userId changes
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));

    // Cleanup function
    return () => {
      console.log('Cleanup');
    };
  }, [userId]); // Dependency array

  return <div>{user?.name}</div>;
}

React Router

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users/:id" element={<UserProfile />} />
      </Routes>
    </BrowserRouter>
  );
}

Backend Development Review

Topics Covered:

  • Lecture 22: Databases & SQL
  • Lecture 23: Node.js
  • Lecture 24-25: Express
  • Lecture 26: Authentication

SQL Basics

-- Create table
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  username VARCHAR(100) UNIQUE NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Select
SELECT * FROM users WHERE id = 1;

-- Insert
INSERT INTO users (username, email)
VALUES ('thor', 'thor@asgard.com');

-- Update
UPDATE users SET email = 'newemail@asgard.com' WHERE id = 1;

-- Delete
DELETE FROM users WHERE id = 1;

Express.js Basics

const express = require('express');
const app = express();

// Middleware
app.use(express.json());

// Routes
app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

app.post('/api/users', (req, res) => {
  const { username, email } = req.body;
  // Create user...
  res.status(201).json({ message: 'User created' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Express Middleware

// Custom middleware
function logger(req, res, next) {
  console.log(`${req.method} ${req.url}`);
  next();
}

app.use(logger);

// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

// Route-specific middleware
function isAuthenticated(req, res, next) {
  if (req.session.userId) {
    next();
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
}

app.get('/api/protected', isAuthenticated, (req, res) => {
  res.json({ data: 'secret' });
});

Authentication with Passport

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

passport.use(
  new LocalStrategy(async (username, password, done) => {
    try {
      const user = await User.findOne({ username });
      if (!user) {
        return done(null, false, { message: 'Incorrect username' });
      }

      const match = await bcrypt.compare(password, user.password);
      if (!match) {
        return done(null, false, { message: 'Incorrect password' });
      }

      return done(null, user);
    } catch(err) {
      return done(err);
    }
  })
);

passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
  const user = await User.findById(id);
  done(null, user);
});

JWT Authentication

const jwt = require('jsonwebtoken');

// Create token
const token = jwt.sign(
  { userId: user.id, username: user.username },
  process.env.JWT_SECRET,
  { expiresIn: '1h' }
);

// Verify token middleware
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid token' });
    }
    req.user = user;
    next();
  });
}

Practice Question: React State

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  }

  return <button onClick={handleClick}>{count}</button>;
}

// After clicking the button once, what will count be?

Click to reveal answer

Answer: 1

Explanation: All three setCount calls use the same count value (0). To increment multiple times, use the functional form: setCount(c => c + 1)

Practice Question: Async

async function test() {
  console.log('A');
  await Promise.resolve();
  console.log('B');
}

console.log('C');
test();
console.log('D');

// What is the output order?

Click to reveal answer

Answer: C, A, D, B

Explanation: "C" logs first, then test() is called logging "A", then "D" logs (async functions don't block), then "B" logs after await resolves.

Practice Question: Express Routing

app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.json({ userId });
});

app.get('/users/special', (req, res) => {
  res.json({ message: 'Special route' });
});

// What happens when you visit /users/special?

Click to reveal answer

Answer: Returns {"userId": "special"}

Explanation: The first route matches and treats "special" as an id parameter. Specific routes should be defined BEFORE parameterized routes!

Practice Question: SQL Joins

-- Given these tables:
-- users: id, username
-- posts: id, user_id, title

-- What does this query return?
SELECT users.username, posts.title
FROM users
LEFT JOIN posts ON users.id = posts.user_id;

-- vs

SELECT users.username, posts.title
FROM users
INNER JOIN posts ON users.id = posts.user_id;

Click to reveal answer

Answer:

LEFT JOIN: Returns ALL users, even if they have no posts (with NULL for post fields)

INNER JOIN: Returns ONLY users who have posts

Practice Question: useEffect

function Component({ id }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetchData(id).then(setData);
  }, []); // Empty dependency array

  return <div>{data}</div>;
}

// What's wrong with this code?

Click to reveal answer

Problem: Missing dependency!

The effect uses id but doesn't include it in the dependency array. If id changes, the effect won't re-run. Should be: useEffect(() => { ... }, [id])

Practice Question: Closures

function createCounter() {
  let count = 0;

  return {
    increment() {
      count++;
    },
    getCount() {
      return count;
    }
  };
}

const counter1 = createCounter();
const counter2 = createCounter();

counter1.increment();
counter1.increment();
counter2.increment();

// What do these output?
console.log(counter1.getCount());
console.log(counter2.getCount());

Click to reveal answer

Answer: 2 and 1

Explanation: Each factory function call creates a new closure with its own count variable. They don't share state.

How to Study for the Final

Study Strategy:

  • âś… Review all lecture slides (especially code examples)
  • âś… Revisit your completed homework projects
  • âś… Practice writing code from memory
  • âś… Work through the practice questions in this review
  • âś… Understand concepts, don't just memorize syntax
  • âś… Review React hooks (useState, useEffect)
  • âś… Practice Express routing and middleware
  • âś… Review authentication flows (Passport, JWT)
  • âś… Practice SQL queries (SELECT, JOIN, INSERT, UPDATE)
  • âś… Understand async/await and promises

Key Concepts to Master

Frontend

  • React component lifecycle
  • Props vs State
  • useEffect dependencies
  • Event handling in React
  • React Router navigation
  • CSS Grid & Flexbox
  • Responsive design patterns

Backend

  • Express routing & middleware
  • SQL queries and joins
  • Authentication strategies
  • Session vs Token auth
  • Password hashing (bcrypt)
  • RESTful API design
  • Error handling

Exam Format & Tips

What to Expect

  • Multiple choice questions
  • Code reading/analysis
  • Short answer coding
  • Debugging exercises
  • Conceptual questions
  • Full-stack integration

Test-Taking Tips

  • Read questions carefully
  • Check for edge cases
  • Trace code execution
  • Don't overcomplicate
  • Manage your time
  • Review your answers

You're Ready for Ragnarök

Like the gods facing Ragnarök, you've trained all semester for this moment.

You've mastered HTML, CSS, JavaScript, React, Node.js, Express, databases, and authentication.

You've built projects, debugged errors, and solved problems.

Trust your training. You've got this! đź’Ş

đź“… Final Exam Date: TBD

📚 Coverage: All material from Lectures 1-29

🎓 Good luck! May the Norns weave success into your fate!