Dynamic Allocation and Reallocation of Memory
Code would have benefited with allocateList(), allocateParticles(), freeList(), freeParticles() functions, rather than trying coding it all up in main().

allocation and deallocation of memory correct?

Allocations lack error checking.  This is the big issue as it also impacts proper de-allocation code.

plist-&gt;list[k] = realloc(plist-&gt;list[k], .... leak memory should the allocation fail.

DIM*plist-&gt;counter[k])*sizeof(plist-&gt;list[k][0]) could overflow int math, yet work well with size_t math.  See re-ordering below.

Robust-ness

Use size_t instead of int for indexing. As "code snippet of a larger program", the array sizes may be very large.  The best type for array indexing is size_t, an unsigned type.

// for(int d=0; d&lt;DIM; d++){
for(size_t d=0; d&lt;DIM; d++){
        p[i].x[d] = frand();
        p[i].v[d] = frand();
    }


Simplification

C does not require the cast nor () with sizeof object

// Particle *p = (Particle*)malloc(N*sizeof(*p));
Particle *p = malloc(N * sizeof *p);

// I prefer `sizeof` first, especially  for math that may benefit with `size_t` math.
//                   v---------------v all size_t math  
Particle *p = malloc(sizeof *p * N * N);
// vs                v---v may overflow `int` math
Particle *p = malloc(N * N * sizeof *p);


Simplify coding, review and maintenance.  Is plist.list a double *?  Why bother attempting to co-coordinate the type of member list with the malloc() call which may be in another file?

// plist.list = malloc(sizeof(double*)*numprocs);
plist.list = malloc(sizeof *(plist.list) * numprocs); 

// plist.counter = calloc(numprocs, sizeof(int));
plist.counter = calloc(numprocs, sizeof *(plist.counter));
// or 
plist.counter = calloc(numprocs, sizeof plist.counter[0]);


The *1 is curious and lacks benefit

// plist.list[k] = malloc(sizeof(double)*1);
plist.list[k] = malloc(sizeof *(plist.list[k]));




buildList() would benefit with re-write - IMO, is is not simple to follow.  As code lacks guiding comments, I am left to guess how to simplify it without losing meaning.  Sample idea with some style improvements.

// Style issue: Avoid wrapping presentation code and horizontal scroll bars
void buildList(Particle *p, size_t n, ParticleList *plist, size_t numprocs) {
  for (size_t np=0; np&lt;numprocs; np++) {
    for (size_t i=0; i&lt;n; i++){
      printf("np %zu, i %zu\n", np, i);
      if (rand()%10 &lt; 5) {
        size_t new_count = plist-&gt;counter[k] + 1;
        void *new_ptr = realloc(plist-&gt;list[np],sizeof plist-&gt;list[np][0] * DIM*new_count);
        if (new_ptr == NULL) {
          TBD_ErrorHandler();
          // ...
        }
        plist-&gt;list[k] = new_ptr;
        plist-&gt;counter[k] = new_count;
        // I think the following is the same - I had trouble following original code.
        for(size_t dm = 0; dm &lt; DIM; dm++) {
          plist-&gt;list[pn][new_count + j] = p[i].x[dm];
        }
      } // endif
    }
  }
}


"l" in "%lf" not required - more of a style issue

// printf("id: %d m: %lf x: %lf %lf v: %lf %lf\n", 
printf("id: %d m: %f x: %f %f v: %f %f\n", 
  p[i].id, p[i].m, p[i].x[0], p[i].x[1], p[i].v[0], p[i].v[1]);




Sample ParticleList_free()

void ParticleList_free(ParticleList *pl) {
  if (pl) {
    if (pl-&gt;list) {
       for(int k=0; k&lt;numprocs; k++) {
         free(pl-&gt;list[k]);
         pl-&gt;list[k] = NULL; // ***
       }
       free(pl-&gt;list);
       pl-&gt;list = NULL; // ***
    }
    free(pl&gt;counter);
    pl&gt;counter = NULL; // ***
    free(pl);
  }

  // Sample usage
  ParticleList *pl = ParticleList_alloc();
  buildList(p, pl, numprocs, N);
  ....
  ParticleList_free(pl);
  pl = NULL; // ***


*** Useful for debugging, but not needed for production code.  By setting to NULL, errant uses of pointers are typically more likely to be found.  YMMV.