Arduino uses 2 types of strings, ordinary C code strings, and String objects. Both use memory in not-so-obvious ways.
C strings are allocated in RAM by default. Sometimes that's important, like when passing a string to the SD library, since it will access RAM to read your string. But many places accept a special type of string allocated only in flash, which saves RAM. When F() isn't supported, you'll get compiler errors. They may be very cryptic... just read any error that results from F() as "this function doesn't support F()". To try this, just put F() around your string. For example:
Code:
Keyboard.print(F("this string does not consume precious RAM"));
Ordinary C strings are statically allocated, which means you'll see the memory they consume reported in the memory estimate that appears every time you compile. Teensy 2.0 has only 2560 bytes of RAM.... it goes quickly once you start manipulating text and using libraries which need memory. Placing F() around every string constant you can will help a lot.
String objects are completely different. They are dynamically allocated, so you will not see their usage in the memory estimate. In theory, they use only the memory needed for whatever text they're storing. In practice, things don't always work out so perfectly. If a String needs to increase its storage, and something else (likely another String) is in memory right after it, the memory allocator will need to allocate a new chunk of memory, leaving an unused hole where the String used to be located. This is called memory fragmentation.... in really bad cases, a lot of the memory ends up being little unusable holes too small to be reused.
The main thing you can do about fragmentation from String is using String's reserve function. For example, something like this might be used in setup() before you start storing data:
Code:
lasttwenletters.reserve(24);
That will allocate space for up to 24 characters. Then as you add and remove characters, as long as you don't store more than 24, that String will not need to allocate new space. If you are having fragmentation problems, even using reserve() on just the important Strings can really help. Usually strings that retain data for a long time, or strings that grow in length as you process text, are the best candidates for reserve(). Strings that store data on a very temporary basis, especially if you're not growing the length of the string as you go, usually aren't helped by reserve().
Of course, it's also quite possible to simply not have enough RAM. F() and reserve() are the easy steps to try. Restructuring your program might help, if you know you're storing data needlessly. But with all optimization work, it gets increasingly more difficult and usually you get diminishing returns as you go. When that happens, upgrading to more memory is usually the most time-effective solution. A Teensy 3.0 has about 14K RAM usable, which is a lot more than the 2.5K you're working with now.