What decoding does, and why it fails
Each %XX stands for one byte. Decoding collects those bytes and reads them as UTF-8, which is how a run like %E4%BD%A0 becomes the single character 你. JavaScript's decodeURIComponent throws "URI malformed" the moment a sequence is not valid UTF-8, for example a character cut in half such as %E4%BD, a stray % or a Latin-1 byte like %E9.
This tool decodes everything that is valid and leaves the broken sequences exactly as typed, then lists them. That is usually enough to see what is wrong with the original text.
Plus signs, %2B and %20
In form data and in most query strings, + means a space. In a path, a + is a plus sign. A literal plus inside a query value is written %2B. Because the right reading depends on where the text came from, plus signs are kept by default, with a note, and one switch turns them into spaces.
A quick check: if the text came from a search box or a form (q=red+shoes), turn the switch on. If it came from a path (/c++/guide), leave it off.
Text that was encoded twice
If decoding leaves %25 or %20 behind, the text was probably encoded twice: the first pass wrote %20 for a space, and the second wrote %25 for the percent sign, giving %2520. This happens when a value is encoded by hand and then again by a library, or when a redirect passes a URL through a second encoder.
Decode the result again to peel off the second layer. The tool shows a button for it whenever it sees leftover escapes. Note that a literal %20 can also be real text, so decode again only when the result still looks encoded.