Evolution of an Anki Card

February 1, 2024 (25d ago)

 · 

6 minutes read

Back

Spaced Repetition

Last year, I discovered Anki, which is software for managing digital flashcards. The problem it tries to tackle is that of memorizing. Anki makes memory a choice. This sounds too good to be accurate, but I've found it true in practice. There are plenty of resources that explain how to use spaced repetition and why it's useful, so I won't be getting into that. If you've never heard of the term, or if you haven't had the motivation, I suggest you at least try it for a few days.

Arguments as to why spaced repetition is great come from Cognitive Science and thus, anything that tries to explain why Anki is good, usually has to dive into some of the research on this field. Bonus tip: if you are a programmer, this article is super enlightening.

It's good to note that Anki is not only useful for remembering simple facts. You can understand complex subjects by using flashcards. A great quote by Michael Nielsen about this:

It's possible to develop virtuoso skill using Anki, a skill aimed at understanding complex material in depth, not just memorizing simple facts.

And he shows so in a very insightful article.

Anki Cards

A flashcard is a card that has a question on its front side and the answer on its back, like so:

Q: What is the world's population?
A: 8.1 billion as of January 31, 2024.

Simple enough, right? The interesting thing that makes flashcards work is how you manage their review cadence. Memory works in a way that makes recall speed and accuracy decline with time until you can no longer remember.

It turns out that spaced repetition counters this effect, so to remember something, you just need to review it once in a while, and every time you remember the fact, you'll recall it more efficiently for a longer period.

If that sounds like a mouthful, maybe this chart helps:

The probability of recalling a fact with the passage of time. Each
test resets the probability to 1.

There are a few algorithms for the cadence of reviews, but the one I'm using is FSRS, which you can use with Anki.

Evolving a Card

The Problem

The hardest part when I started using Anki was figuring out how to actually make the cards. Even after reading all the links posted throughout this article, I still had many questions and had zero confidence in creating good flashcards. I even went and read rules for formulating knowledge, which is a guide to creating good learning material. An excerpt from the article:

The speed of learning will depend on the way you formulate the material. The same material can be learned many times faster if well formulated! The difference in speed can be stunning!

I immediately made use of my shiny new Anki app and ankified (created flashcards for) said rules. However, this proved not to be helpful. Even if I memorized them, I wasn't learning them. So, only after two or three months of practice have I found a few rules that cater to my style and that I'll share in a second.

So, if you find yourself in this situation, my advice is to just create the cards without trying to be perfect, and you'll develop a sense of what works for you and what doesn't pretty quickly. Just trust the process. Using Anki is a skill in itself, and so it can be learned with patience.

An Example

"Okay, that's all good, but I wanna know how you do it". Alright, let's start with a simple card. The topic is Rust's standard library, so you might not be familiar with it but don't focus on the content; focus on the structure of the cards.

What follows was the first card on the

Rust std
deck (a deck is a collection of flashcards):

  Q: What is a `Cursor<T>`?
  A: A `Cursor` wraps an in-memory buffer and provides it 
with a `Seek` implementation.

Cursors are used with in-memory buffers, anything implementing
`AsRef<[u8]>`, to allow them to implement `Read` and/or `Write`,
allowing these buffers to be used anywhere you might use a
reader or writer that does actual I/O.

The standard library implements some I/O traits on various
types which are commonly used as a buffer, 
like `Cursor<Vec<u8>>` and `Cursor<&[u8]>`.

You can immediately tell I am not learning all of that at once. It took me a few reviews to realize so, even though I did remember a lot, it was fuzzy and unclear.

An easy improvement is to split the card. How big should they be? As short as possible. When recalling, you'll make sense of it anyway, so the idea is to keep it as terse as you can while keeping semantics intact (though I've found it's okay to keep extra words). I split the above into:

Q: What is a `Cursor<T>`?
A: `Cursor` wraps an in-memory buffer to give it a `Seek` implementation.
---
Q: What is an in-memory buffer?
A: `AsRef<[u8]>`
---
Q: Why use a `Cursor<T>`?
A: Implement `Read` and/or `Write`.
---
Q: What types are commonly used as buffers?
A: `Cursor<Vec<u8>>` and `Cursor<&[u8]>`.

It might seem unintuitive to think that it is easier to memorize four cards than just one. However, our recall will be much easier now since the context we need to remember per question is much less. It also means that these are "chainable", as thoughts are: you first think about what a Cursor is, which gives leeway into what you use it for, and so on.

These cards are in better shape now, but there are gaps that we need to fill. For example, if you don't know what

Seek
is, you don't really understand the answer to the first card. The more context we provide for a card, both in the question and the answer, the easier our brain commits the knowledge to memory, and the faster we chunk information (you can learn more about this here). This context is not only adding tags or cues to the card but also understanding the concepts that the card relates to.

There is another substantial improvement. If we only ever recall the front-to-back direction of cards that contain equivalence relations, we will indeed remember the front-to-back relationship, but it will be hard to recall the back-to-front one. For example, if we only practice the

in-memory buffer -> AsRef<[u8]>
relationship, we might not remember that
AsRef<[u8]>
can indeed be an
in-memory buffer
. This also happens a lot in language practice, for instance. You might recognize that
mesa
is
table
; however, when asked to say
The table is over there
, you struggle to find the word for
table
in Spanish. This is resolved by adding another card that goes from back to front.

So, with all of these in mind, the second flashcard above would result in:

Q: Rust: What is an in-memory buffer?
A: `AsRef<[u8]>`
---
Q: Rust: `AsRef<[u8]>`
A: What is an in-memory buffer?

Interference

One final thing that is hard to avoid is memory interference. In the example above, you might find that it's hard to remember which of these two answers is right:

Q: Why use a `Cursor<T>`?
---
A: `Cursor` wraps an in-memory buffer to give it a `Seek` implementation.
A: Implement `Read` and/or `Write`.

This happens because both cards are quite similar. You can answer both "What is a

Cursor<T>
?" and "Why use a
Cursor<T>
?" with "
Cursor
wraps an in-memory buffer...". You'll find that this happens very often, and I avoid it by making the cards more precise. That is, you add more context or change the wording to clarify the concept. For example, I would change one of the flashcards above like this:

Q: Why would you need a `Cursor<T>`?
A: Sometimes you must implement `Read` and/or `Write`.

Conclusion

Hopefully, this article helps you get faster to a point where you can confidently create flashcards, avoiding some of the pitfalls I've already been through. If not, that's okay anyway; you'll figure it out with time.

See you in the next one!