One of the biggest challenges of programming microcontroller is the very limited quantity of RAM that they offer. For example, a classic Arduino UNO has only 2KB of RAM!

That’s why it’s imperative to use special techniques to consume less memory in our program. Some of these techniques are counter-intuitive, and most are radically different from what you usually think is right. If you are used to writing applications for computers, phones, or tablet, you need to adopt a different mindset and challenge your assumptions.

In this page, we’ll see how we can write an Arduino program that consumes less memory. Of course, the focus is on JSON serialization and deserialization, so we’ll see how we can use ArduinoJson with less RAM.

Tip 1: Avoid duplication if the input is in memory

Remember that ArduinoJson’s deserializer has two modes:

  1. the zero-copy mode, used when the input is writeable (char*)
  2. the normal mode, used when the input is read-only (const char*, String)

In the second mode, the deserializer duplicates the relevant parts of the input (basically everything except spaces and punctuation) in the JsonBuffer. So, to get the most efficient program, you must use the first mode: the zero-copy mode.

Good: zero-copy

char[] json = "{\"hello\":\"world\"}";

Bad: duplication

const char* json = "{\"hello\":\"world\"}";

Bad: duplication

String json = "{\"hello\":\"world\"}";

See also

Tip 2: Pass Stream directly to the deserializer

A stream (Stream or std::istream) is a source of volatile bytes, so its content needs to be copied in RAM.

The best thing to do is to let ArduinoJson do the duplication as it ignores everything that s not required: punctuation, spaces, and comments.

To do that, simply pass the stream to parseArray() or parseObject()`.

Good: pass the Stream directly

File file =;

Bad: pass a copy of the input

char buffer[256];
File file =;, 256);
DynamicJsonBuffer jsonBuffer;

See also:

Tip 3: Prefer stack to heap memory

Allocating and deallocating in the heap cause overhead and fragmentation, so the program case use much less RAM that there is actually on the device. Heap allocation happens anytime you use malloc(), new and String.

ArduinoJson uses the stack with StaticJsonBuffer and the heap for DynamicJsonBuffer. For small JsonBuffer (let’s say under 1KB), prefer a StaticJsonBuffer.

If you’re using a microcontroller with very limited RAM (for example the ATmega328 of an Arduino UNO), you should not use the heap at all.

Good: only stack memory

char[] json = "{\"hello\":\"world\"}";
StaticJsonBuffer<200> jsonBuffer;

Bad: only heap memory

String json = "{\"hello\":\"world\"}";
DynamicJsonBuffer jsonBuffer;

See also

  • Mastering ArduinoJson, chapter 2: The Missing C++ Course explains in detail what stack and heap memory.

Tip 4: Deserialize in chunks

One neat feature of ArduinoJson is that, when it parses an object from a Stream, it stops reading when it encounters the closing }, and the same is true for arrays.

Using this feature, you don’t have to deserialize the whole JSON document at once. Instead, you can parse only a part of it and repeat the operation.

This technique works great when your input contains an array of nested objects. For example, if you want to parse the huge response of a 10-day forecast of Weather Underground, you can skip the beginning until you see "forecastday": [ in the stream (use Stream::find()), and then parse the objects for each day one after the other.

As usual, don’t reuse the JsonBuffer, declare it inside the loop.

See also

Tip 5: Avoid duplication of Flash strings

At first sight, Flash (or PROGMEM) strings looks excellent to reduce the RAM usage of a program. Indeed, the principle of such string is to reside in the Flash memory which is much bigger than the RAM.

Unfortunately, Flash memory uses a different address space, so software that manipulates both RAM and Flash strings needs to temporarily copy the Flash strings into the RAM. For example, the String class makes a copy in the heap, and ArduinoJson makes a copy in the JsonBuffer.

Let’s compare the two following lines:

array.add("value"); // ArduinoJson stores a pointer
array.add(F("value")); // ArduinoJson duplicates the string

In the first line, because the strings are in RAM, ArduinoJson can simply store pointers to the strings. But, in the second line, because the strings are in Flash, ArduinoJson must make a copy of them in the RAM.

However, with just these two lines, there is no real problem; but look what happens if we do the same thing in a loop:

 // ArduinoJson stores 10 pointers
for (int i=0; i<10; i++) {

// ArduinoJson stores 10 pointers
for (int i=0; i<10; i++) {

Here is the problem with Flash strings, as ArduinoJson is not able to see that the same string is inserted several times, it makes several copies of the Flash string.

Flash strings are a double-edged sword. If used correctly, they can save RAM; but, most of the time, they make the problem worse.

My advice is the following: don’t use Flash strings for keys and values, only use them for log messages and similar strings.

See also

  • Mastering ArduinoJson, chapter 2: The missing C++ course explains what are the different kinds of strings in Arduino, and how to use them properly

Tip 6: Avoid global variable

Global variables are bad on many levels; we’ll only look at the memory usage.

The problem with global variables is that, by definition, they live during the whole execution of the program. In other words, they always consume RAM, whether the program uses them or not. Contrast that with local variables that have a short lifespan; they only consume memory when the program needs them.

My advice is: reduce the number and the size of global variables to the strict minimum.

See also

Tip 7: Avoid duplication of String

Remember that the String class always makes a copy of the string passed to the constructor.

For example, the following line make a copy of “hello world”:

String s = "hello world"; // one copy

Yes, it means that “hello world” is present twice in RAM: in the global section and in the heap. If you just need to give a name to this variable, it’s better to use the actual type of the string literal, which is const char*:

const char* s = "hello world"; // no copy

Another common mistake is to declare a function that takes a parameter of type String by value:

void f(String s) {

Because the function parameter is a value (not a pointer, nor a reference), each invocation of the function creates a new String, thereby creating another copy of the string. To avoid this useless duplication, use a const-reference:

void f(const String& s) {

See also