Do you struggle to remember what you’ve learned? Whether you're studying for exams, learning a new language, or mastering complex concepts, retaining information can feel like an uphill battle. But what if your study tool could adapt to your memory, helping you focus on what you find difficult while reviewing easier concepts less often?
Enter spaced repetition—a scientifically proven technique that optimizes learning by scheduling reviews at strategic intervals. Today, we’ll build a smart flashcard app that uses this powerful method to help you remember more with less effort. Let’s dive in! 🚀
What is Spaced Repetition? ⏳
Spaced repetition is a learning technique that spaces out reviews of material over time, based on how well you know it. Here’s how it works:
- Difficult concepts: Reviewed more frequently.
- Easier concepts: Reviewed less often.
- Adaptive scheduling: The system adjusts to your memory patterns.
This method is rooted in the spacing effect, a psychological principle that shows we retain information better when learning is spread out over time. By focusing on what you struggle with and spacing out reviews of what you know well, spaced repetition makes your study sessions more efficient and effective. 🧠✨
Introducing Flashcard Pro 📚
Our app, Flashcard Pro, is a simple yet powerful tool that helps you:
- Create custom flashcards with questions and answers.
- Review cards using a spaced repetition algorithm.
- Track your progress with easy-to-read statistics.
- Save all your data locally in the browser.
You can try the live demo here: Flashcard Pro.
Let’s break down how we built this app step by step!
Building the Foundation: HTML 🏗️
The HTML file lays the groundwork for our app. It includes:
- A header with the app title and stats (total cards and due cards).
- A creation section for adding new flashcards.
- A cards section to display and review flashcards.
- An explanation section that describes how the review system works.
Here’s the basic structure:
</span>
lang="en">
charset="UTF-8">
name="viewport" content="width=device-width, initial-scale=1.0">
Flashcard Pro
rel="stylesheet" href="styles.css">
class="container">
Flashcard Pro
class="stats">
Total: id="total-cards">0
Due: id="due-cards">0
class="create-section">
class="input-group">
type="text" id="front" placeholder="Question/Front">
type="text" id="back" placeholder="Answer/Back">
id="add-card">Add Card
class="cards-section">
Your Cards
class="cards-list" id="cards-list">
class="review-controls" id="review-controls" style="display: none;">
class="review-btn" data-difficulty="1">Hard
class="review-btn" data-difficulty="2">Medium
class="review-btn" data-difficulty="4">Easy
class="explanation-section">
How Reviews Work
class="hard-text">Hard: Review tomorrow (struggled).
class="medium-text">Medium: Review soon (some effort).
class="easy-text">Easy: Review later (knew it well).
<span class="na">src="script.js">
Enter fullscreen mode
Exit fullscreen mode
This clean and intuitive layout makes it easy for users to navigate and interact with the app.
Styling the App: CSS 🎨
The CSS file brings our app to life with a modern, user-friendly design. Key features include:
A gradient background for a sleek, professional look.
Interactive buttons with hover effects for better usability.
Color-coded review controls to help users quickly identify difficulty levels.
A card flip effect that mimics real-life flashcards.
Here’s a glimpse of the styling:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
background: linear-gradient(120deg, #f6f8fc, #e9eef5);
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
}
.container {
background: white;
border-radius: 15px;
padding: 25px;
width: 100%;
max-width: 900px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
h1 {
color: #2c3e50;
font-size: 2rem;
font-weight: 600;
}
.stats span {
margin-left: 15px;
color: #7f8c8d;
font-size: 0.9rem;
}
.create-section {
margin-bottom: 30px;
}
.input-group {
display: flex;
gap: 10px;
align-items: center;
}
input {
flex: 1;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
input:focus {
outline: none;
border-color: #3498db;
}
#add-card {
background: #3498db;
color: white;
border: none;
padding: 12px 25px;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: background 0.3s ease;
}
#add-card:hover {
background: #2980b9;
}
.cards-section h2 {
color: #2c3e50;
font-size: 1.5rem;
margin-bottom: 15px;
}
.cards-list {
max-height: 400px;
overflow-y: auto;
padding-right: 10px;
}
.card-item {
background: #f9fbfc;
border-radius: 10px;
padding: 15px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: transform 0.2s ease, background 0.2s ease;
}
.card-item:hover {
transform: translateX(5px);
background: #f1f5f9;
}
.card-content {
flex: 1;
}
.card-front {
font-weight: 500;
color: #2c3e50;
margin-bottom: 5px;
}
.card-back {
color: #7f8c8d;
font-size: 0.9rem;
display: none;
}
.card-item.show-back .card-back {
display: block;
}
.card-status {
font-size: 0.8rem;
color: #95a5a6;
margin-left: 15px;
}
.delete-btn {
background: #e74c3c;
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
font-size: 0.8rem;
transition: background 0.3s ease;
}
.delete-btn:hover {
background: #c0392b;
}
.review-controls {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 15px;
}
.review-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
color: white;
cursor: pointer;
font-weight: 500;
transition: transform 0.2s ease;
}
.review-btn[data-difficulty="1"] { background: #e74c3c; }
.review-btn[data-difficulty="2"] { background: #f1c40f; }
.review-btn[data-difficulty="4"] { background: #2ecc71; }
.review-btn:hover {
transform: translateY(-2px);
}
.explanation-section {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.explanation-section h3 {
color: #2c3e50;
font-size: 1.2rem;
margin-bottom: 10px;
}
.explanation-section p {
color: #7f8c8d;
font-size: 0.9rem;
margin-bottom: 5px;
}
.hard-text {
color: #e74c3c;
font-weight: 600;
}
.medium-text {
color: #f1c40f;
font-weight: 600;
}
.easy-text {
color: #2ecc71;
font-weight: 600;
}
Enter fullscreen mode
Exit fullscreen mode
The design is clean and distraction-free, creating an optimal environment for focused learning.
Powering the App: JavaScript ⚙️
class Flashcard {
constructor(front, back) {
this.front = front;
this.back = back;
this.interval = 1;
this.nextReview = new Date();
this.ease = 2.5;
}
}
class FlashcardApp {
constructor() {
this.cards = JSON.parse(localStorage.getItem('flashcards')) || [];
this.cards = this.cards.map(card => ({
...card,
nextReview: new Date(card.nextReview)
}));
this.selectedCard = null;
this.initElements();
this.bindEvents();
this.renderCards();
this.updateStats();
}
initElements() {
this.frontInput = document.getElementById('front');
this.backInput = document.getElementById('back');
this.addButton = document.getElementById('add-card');
this.cardsList = document.getElementById('cards-list');
this.totalCardsSpan = document.getElementById('total-cards');
this.dueCardsSpan = document.getElementById('due-cards');
this.reviewControls = document.getElementById('review-controls');
this.reviewButtons = document.querySelectorAll('.review-btn');
}
bindEvents() {
if (this.addButton) {
this.addButton.addEventListener('click', () => this.addCard());
} else {
console.error('Add Card button not found in the DOM');
}
this.reviewButtons.forEach(btn =>
btn.addEventListener('click', (e) => this.reviewCard(e.target.dataset.difficulty))
);
}
addCard() {
const front = this.frontInput.value.trim();
const back = this.backInput.value.trim();
if (front && back) {
const card = new Flashcard(front, back);
this.cards.push(card);
this.saveCards();
this.renderCards();
this.updateStats();
this.clearInputs();
} else {
console.log('Please enter both front and back text');
}
}
deleteCard(index) {
this.cards.splice(index, 1);
this.saveCards();
this.renderCards();
this.updateStats();
if (this.selectedCard) {
this.reviewControls.style.display = 'none';
this.selectedCard = null;
}
}
renderCards() {
this.cardsList.innerHTML = '';
const now = new Date();
this.cards.forEach((card, index) => {
const cardElement = document.createElement('div');
cardElement.className = 'card-item';
cardElement.innerHTML = `
${card.front}
${card.back}
${card.nextReview <= now ? 'Due Now' : `Due: ${card.nextReview.toLocaleDateString()}`}
${index}">Delete
`;
cardElement.addEventListener('click', (e) => {
if (!e.target.classList.contains('delete-btn')) {
this.selectCard(card, cardElement);
}
});
const deleteBtn = cardElement.querySelector('.delete-btn');
deleteBtn.addEventListener('click', () => this.deleteCard(index));
this.cardsList.appendChild(cardElement);
});
}
selectCard(card, element) {
if (this.selectedCard === element) {
element.classList.toggle('show-back');
return;
}
if (this.selectedCard) {
this.selectedCard.classList.remove('show-back', 'selected');
}
this.selectedCard = element;
element.classList.add('show-back', 'selected');
this.reviewControls.style.display = 'flex';
}
reviewCard(difficulty) {
if (!this.selectedCard) return;
const index = Array.from(this.cardsList.children).indexOf(this.selectedCard);
const card = this.cards[index];
const now = new Date();
if (difficulty >= 3) {
card.ease = Math.max(1.3, card.ease + 0.1 * (difficulty - card.ease));
card.interval = card.interval * card.ease;
} else {
card.ease = Math.max(1.3, card.ease - 0.2);
card.interval = 1;
}
card.nextReview = new Date(now.getTime() + card.interval * 24 * 60 * 60 * 1000);
this.saveCards();
this.renderCards();
this.updateStats();
this.reviewControls.style.display = 'none';
this.selectedCard = null;
}
updateStats() {
const now = new Date();
this.totalCardsSpan.textContent = this.cards.length;
this.dueCardsSpan.textContent = this.cards.filter(card => card.nextReview <= now).length;
}
clearInputs() {
this.frontInput.value = '';
this.backInput.value = '';
}
saveCards() {
localStorage.setItem('flashcards', JSON.stringify(this.cards));
}
}
document.addEventListener('DOMContentLoaded', () => {
try {
new FlashcardApp();
} catch (error) {
console.error('Error initializing FlashcardApp:', error);
}
});
Enter fullscreen mode
Exit fullscreen mode
The JavaScript file is where the magic happens. It handles:
Creating and deleting flashcards.
Implementing the spaced repetition algorithm.
Saving flashcards to local storage for persistence.
Updating the review schedule based on user feedback.
Here’s an overview of the key components:
The Flashcard Class
This class represents individual flashcards, storing:
The front (question) and back (answer).
The review interval (time until the next review).
The next review date.
An ease factor that determines how quickly the interval grows.
The FlashcardApp Class
This is the main class that manages the app’s functionality. Key methods include:
Initialization and Setup
constructor() {
// Loads cards from localStorage
// Sets up DOM elements and event listeners
// Renders existing cards and updates statistics
}
Enter fullscreen mode
Exit fullscreen mode
This method initializes the app, loading saved cards and setting up the user interface.
Creating and Deleting Cards
addCard() {
// Creates a new flashcard from input values
// Adds it to the collection and updates the UI
}
deleteCard(index) {
// Removes a card at the specified index
// Updates the UI and statistics
}
Enter fullscreen mode
Exit fullscreen mode
These methods allow users to create and delete flashcards, ensuring the UI and data stay in sync.
Reviewing Flashcards
reviewCard(difficulty) {
// Adjusts the ease factor based on difficulty
// Calculates the new interval and next review date
// Updates the UI and saves changes
}
Enter fullscreen mode
Exit fullscreen mode
This method implements the spaced repetition algorithm, adjusting review intervals based on how well the user knows each card.
The Spaced Repetition Algorithm 🧠
The heart of our app is the spaced repetition algorithm. Here’s how it works:
Hard: Review the card tomorrow (struggled to recall).
Medium: Review the card soon (some effort required).
Easy: Review the card later (knew it well).
The algorithm adjusts the review interval based on the user’s feedback, ensuring that difficult cards are reviewed more frequently and easier ones less often. This adaptive approach maximizes learning efficiency. 📈
Saving Data with Local Storage 💾
To ensure flashcards persist between sessions, we use localStorage:
saveCards() {
localStorage.setItem('flashcards', JSON.stringify(this.cards));
}
Enter fullscreen mode
Exit fullscreen mode
This method saves the flashcards as a JSON string, allowing users to pick up right where they left off.
How to Use Flashcard Pro 📝
Using the app is simple:
Create cards: Enter a question and answer.
Review cards: Click on a card to see the answer.
Rate your recall: Mark the card as Hard, Medium, or Easy.
Trust the system: The app schedules reviews based on your ratings.
Ideas for Improvement 🚀
Want to take this app to the next level? Here are some ideas:
Categories and Tags: Organize cards by topic or subject.
Rich Text Support: Add formatting, images, or equations.
Import/Export: Share decks or back up your data.
Mobile Optimization: Make it a PWA for better mobile use.
Advanced Stats: Track progress with graphs and analytics.
The Science Behind Spaced Repetition 🔬
Spaced repetition is based on the forgetting curve, a concept discovered by psychologist Hermann Ebbinghaus. It shows how we lose information over time without review. By interrupting this process at optimal intervals, spaced repetition strengthens memory and improves retention. Studies show it can boost memory retention by 200-400% compared to traditional methods. 📊
Final Thoughts 🎓
Building a flashcard app with spaced repetition is more than just a coding project—it’s creating a tool that can transform how we learn. By combining clean design with a powerful algorithm, Flashcard Pro helps users study smarter, not harder.Whether you're learning a new skill or preparing for exams, spaced repetition can make a huge difference. So why not give it a try? Happy coding and happy learning! 🚀✨